Shopify Ajax API, Alpine JS, add user selected options to cart using Ajax - ajax

I am building this Build A Box section where a user can select various items that make up 1 product. I'm having trouble getting items into the cart.
UPDATE:
Current state is can add the hard coded main product in formData I did this first to get addToCart started. It’s adds the main product but does not redirect to cart page. If you change url in browser to /cart its in there.
I need to figure out 2 things.
1 how to redirect to cart page after firing addToCart()
2 How to add user selected items as 1 product to cart not variants.
See Logs
I can select elements and push into an array in my x-data component. The data looks like this:
[
{img_string: 'https://cdn.shopify.com/s/files/1/0695/7495/1222/files/Barky.webp?
v=1672528086&width=150', title: 'Barky', id: 'selected_item-1'},
{img_string: 'https://cdn.shopify.com/s/files/1/0695/7495/1222/files/Barky.webp?
v=1672528086&width=150', title: 'Barky', id: 'selected_item-1'}
]
For my latest iteration, I just hard coded the variant id & a quantity. Still have to figure out how to get all the selected items into the cart.
UPDATE: I added preventDefault filter to Alpine addToCart() call now it does not redirect but if you change url in browser to /cart the hard coded main product is in there.
<div x-data="items: []">
<div
x-data="
{
qty: 1,
addToCart(){
let formData = {
'items': [{
'id': 44202123100470,
'quantity': 1
}]
};
fetch(window.Shopify.routes.root + 'cart/add.js', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => {
console.log(response)
return response.json();
})
.catch((error) => {
console.error('Error:', error);
});
}
}
"
class="tw-mt-12 tw-flex tw-flex-col tw-p-auto tw-bg-brandText tw-opacity-80"
>
<div class="tw-p-8">
<h2 class="tw-mb-4 tw-text-white">Your Selections</h2>
{% assign product_form_id = 'product-form-' | append: section.id %}
{%- form 'product',
product,
id: product_form_id,
class: 'form',
novalidate: 'novalidate',
data-type: 'add-to-cart-form'
-%}
<input
type="hidden"
name="id"
value="{{ product.selected_or_first_available_variant.id }}"
disabled
>
<input type="hidden" name="quantity" x-model="qty" value="1">
<div class="product-form__buttons">
<button
type="add"
#click="addToCart()"
:class="full_box ? 'tw-bg-ctaColor' : ''"
class="tw-bg-white tw-text-brandText tw-flex tw-justify-center tw-py-4 tw-px-6 tw-rounded-full tw-font-bold"
>
<p class="" x-show="!full_box">
<span x-text="items_selected" class="tw-mr-2"></span><span class="tw-mr-2">of</span
><span class="tw-font-bold" x-text="box_size"></span><span class="tw-ml-2">Selected</span>
</p>
<p x-show="full_box" class="">Add to cart</p>
</button>
</div>
{%- endform -%}
</div>
</div>

The way I solved the redirect issue was to use the Location API add location.href="/cart" in the success promise of the Ajax call.
fetch(window.Shopify.routes.root + 'cart/add.js', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => {
console.log(response)
return response.json();
})
.then((data) => {
location.href = '/cart';
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
To solve the issue of how to get all selected items into the cart. I mapped over the items array of objects and returned an array of strings. Then I assigned to a variable selectedItems and called toString()
In the properties key of formData I added a value of selectItems to Box Items key. Kinda ugly at the moment no spaces in string. Definitely need to refactor this. Any feedback would be Super Cool 🏄‍♂️
<div
x-data="
{
addToCart(items, id){
let itemsToAdd = items.map((item) => {
return item['title'];
})
let selectedItems = itemsToAdd.toString()
let formData = {
'items': [{
'id': id,
'quantity': 1,
'properties': {
'Box Items': selectedItems,
}
}]
};
fetch(window.Shopify.routes.root + 'cart/add.js', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
})
.then(response => {
console.log(response)
return response.json();
})
.then((data) => {
location.href = '/cart';
console.log('Success:', data);
})
.catch((error) => {
console.error('Error:', error);
});
reset()
}
}
"
class="tw-mt-12 tw-flex tw-flex-col tw-p-auto tw-bg-brandText tw-opacity-80"
>...</div>

Related

Vue Custom input file component could not be submitted correctly

I have a component for browsing files which I use to select the file I want to upload.
Here is my component:
<template>
<label class="file-select">
<div class="select-button">
<span v-if="value">Selected File: {{value.name}}</span>
<span v-else>Select File</span>
</div>
<input type="file" #change="handleFileChange"/>
</label>
</template>
<script>
export default {
props: {
value: File
},
methods: {
handleFileChange(e) {
this.$emit('input', e.target.files[0])
}
}
}
</script>
Here is how I used my component:
<p>Select Image: <bim-fileinput v-model="file"></bim-fileinput></p>
Here is how I submit the file with axios:
submit: function(){
console.log(localStorage.getItem('token'));
axios.post('/api/employees', {
picture: this.file,
}, { headers: { Authorization: 'Bearer '.concat(localStorage.getItem('token')) }, 'Content-Type': 'multipart/form-data' })
.then(response => {
router.push({ name: "IndexEmployees"});
}).catch(error => {
console.log(error.message);
});
}
After submitting, in controller I get the picture attribute but as an empty array.
so I tried to print it using console.
console.log('File '+ JSON.stringfy(this.file))
I got File {}
as an empty object.
So I need to figure out where is the problem in my code or how to make it correctly.
Thanks in advance.
this.file is instance of File, it's always as {} when json encode.
The problem in axios, you must use FormData to send file.
const formData = new FormData();
formData.append('picture', this.file);
axios.post('/api/employees', formData, {
headers: {
'Content-Type': 'multipart/form-data',
// ...
}
}) // ...

Vue.js add class on specific element

So im creating a basic tasklist where i want to set them done, when the <li>is clicked. When it's clicked i want that a class is added to the <li> thats clicked. i could not figure this out with the docs so i hope someone could help me out :D
The code i already have:
<transition-group name="list">
<li class="list-item list-group-item" v-for="(task, index) in list" :key="task.id" v-on:click="finishTask(task.id)" >
#{{ task.text }}
<button #click="removeTask(task.id)" class="btn btn-danger btn-xs pull-right">Delete</button>
</li>
</transition-group>
</ul>
</div>
// get the csrf token from the meta
var csrf_token = $('meta[name="csrf-token"]').attr('content');
export default {
data() {
return {
list: [],
taskInput: {
id: '',
text: ''
}
};
},
// So the tasks will show when the page is loaded
created() {
this.allTasks();
},
methods: {
// get all the existing tasks
allTasks() {
axios.get('tasks').then((response) => {
this.list = response.data;
});
},
// create a new task
createTask() {
axios({
method: 'post',
url: '/tasks',
data: {
_token: csrf_token,
text: this.taskInput.text,
},
}).then(response => {
this.taskInput.text = '';
this.allTasks();
});
},
// remove the tasks
removeTask(id) {
axios.get('tasks/' + id).then((response) => {
this.allTasks();
});
},
finishTask(id) {
axios({
method: 'post',
url: 'tasks/done/' + id,
data: {
_token: csrf_token,
data: this.taskInput,
},
}).then(response => {
this.allTasks();
});
}
}
}
I know how i should do this with jquery but not with vue js, i hope this aint a to stupid question :D
You can bind css classes and styles, add a Boolean done property to your note object with default value of false, when you click the note change its done property to true. here is an example
new Vue({
el:"#app",
data:{
notes: [
{ text: "First note", done:false },
{ text: "Second note", done:false },
{ text: "Third note", done:false },
{ text: "Fourth note", done:false },
{ text: "Fifth note", done:false }
]
},
methods:{
finishNote(note){
// send your api request
// then update your note
note.done = true
}
}
})
.done{
background-color:green;
}
<script src="https://cdn.jsdelivr.net/npm/vue#2.5.13/dist/vue.min.js"></script>
<div id="app">
<ul>
<li v-for="note in notes" :class="{done:note.done}" #click="finishNote(note)">{{note.text}}</li>
</ul>
</div>
You can use the event argument. Which is automatically provided on your on click method.
onListClicked(event) {
event.target.className += " myClass";
}
Here I did a demo for you: https://jsfiddle.net/6wpbp70g/

laravel search - returning all results even if no match and make delay to ajax

I have a problem with my search.
Problem 1
Currently if I type in the field it is searching however the search never ever stops, so if I type hello, it will make about 500 requests within a minute.
Problem 2
I am searching in film table to find matching 'title' as well as find business name corresponding to business_id in business table.
Problem 3
Each time request is made it brings back master page again i.e. loading all js and css (which might be why it is making so many requests?) but if I don't extend master, result blade doesn't work.
however even if I input 'e' it brings me back 'guardians of the galaxy' which doesn't have 'e' My thoughts are that it is searching throught business table as well somehow. They have both eloquent one to one relationships
Controller:
public function cinema_search($cinema_value) {
$cinema_text = $cinema_value;
if ($cinema_text==NULL) {
$data = Film::all();
} else {
$data = Film::where('title', 'LIKE', '%'.$cinema_text.'%')->with('business')->get();
}
return view('cinemasearch')->with('results',$data);
}
Form::
<form id="cinema_display">
<div class="form-group">
<input type="text" class="form-control" id="search_cinemas" onkeyup="search_cinema(this.value);" placeholder="Search film">
</div>
<div id="show"
</div>
</div>
</form>
ajax:
function search_cinema(cinema_value) {
$.ajax({
url: '/cinemasearch/' + cinema_value,
type: 'post',
dataType: 'html',
success: function(data) {
$('#show').append(data);
$('.se-pre-con').fadeOut('slow', function () {
$(".container").css({ opacity: 1.0 });
});
},
error: function(data) {
},
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
});
}
cinemasearch.blade(results):
#extends('master') #section('title', 'Live Oldham') #section('content')
#section('content')
<table style="width:100%">
#if (isset($results) && count($results) > 0)
#foreach( $results as $film )
<tr>
<td>{{ $film->business->name }}</td>
<td>{{ $film->title }}</td>
<td>{{ $film->times}}</td>
</tr>
#endforeach
#endif
</table>
#endsection
function search_data(search_value) {  
$.ajax({
        url: '/searching/' + search_value,
        type: 'post',
        dataType: 'html',
        success: function(data) {
            $('#show_search_result').append(data);
            $('.se-pre-con').fadeOut('slow', function () {
$(".container").css({ opacity: 1.0 });
            });
        },
        error: function(data) {
            $('body').html(data);
        },
        headers: {
        'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
        }
    });
}
function tram_stops(tram_value) {
    $.ajax({
        url: '/tramsearch/' + tram_value,
        type: 'post',
        dataType: 'html',
        success: function(data) {
            $("#display").html(data);
            var tram_value = tram_value;
        },
        error: function(data) {
            
        },
        headers: {
        'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
        }
    });
}
/*
setInterval(tram_stops, (30 * 1000));
*/
function search_cinema(cinema_value) {
    $.ajax({
        url: '/cinemasearch/' + cinema_value,
        type: 'post',
        dataType: 'html',
        success: function(data) {
                var items = JSON.parse(data);
                var showElement = $('#show');
                showElement.html('');
                $.each(data, function() {
                   showElement.append(this.title +' '+ this.times+'<br />');
                });
        },
        error: function(data) {
        },
        headers: {
        'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
        }
    });
}
You are returning the wrong response type from cinema_search. Ajax expects a JsonResponse, not what the view helper returns which is \Illuminate\Http\Response. Put your search results in:
return response()->json(['results' => $data]);
to start with if you just want the data. If you want to actually return the rendered view file, you would need to do:
return response()->json(['results' => view('cinemasearch')->with('results',$data)->render()]);
then inject that into your DOM. The problem with rendering server side is nothing is bound client side so if you have any interaction requiring JS, you'll need to create those manually in your success callback.
Problem 1:
Remove the keyUp event in your html and add an event in Jquery.
Your HTML structure is not correct
This:
<form id="cinema_display">
<div class="form-group">
<input type="text" class="form-control" id="search_cinemas" onkeyup="search_cinema(this.value);" placeholder="Search film">
</div>
<div id="show"
</div>
</div>
</form>
Should be:
<form id="cinema_display">
<div class="form-group">
<input type="text" class="form-control" id="search_cinemas" onkeyup="search_cinema(this.value);" placeholder="Search film">
<div id="show">
</div>
</div>
</form>
Then again you should consider to remove the onkeyup event. And change add it in Jquery to something like this:
Problem 2 & 3: I would recommend a raw Query and return an json instead of a view. And you shouldn't check if($cinema_text === NULL) this won't be the case ever. Unless you put NULL in your url and even then it will be an String and not NULL and if('NULL' === NULL) returns false look at this post for the diff of == and ===.
public function cinema_search($cinema_value) {
$cinema_text = $cinema_value;
if (empty($cinema_text)) {
$data = Film::all();
} else {
$data = DB::select('*')
->from('films')
->join('businesses', 'businesses.id', '=', 'films.business_id')
->where('films.title', 'LIKE', '%'.$cinema_text.'%')
->orWhere('bussiness.title', 'LIKE', '%'.$cinema_text.'%')
->get();
}
return response()->json(['results' => $data]);
}
Then in your JavaScript do something like this:
$( document ).ready(function() {
console.log( "ready!" );
$( "#search_cinemas" ).change(function() {
search_cinema(this.value);
console.log( "New value"+this.value+"!" );
});
function search_cinema(cinema_value) {
console.log('setup ajax');
$.ajax({
url: '/cinemasearch/' + cinema_value,
type: 'post',
success: function(data) {
console.log('success!');
var showElement = $('#show');
showElement.html('');
$.each(items, function() {
showElement.append(this.title +' '+ this.times+'<br />');
});
},
error: function(data) {
console.log(data);
},
headers: {
'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
}
});
}
});

Deleting record using laravel and vue js

I'm stuck in making a delete method using vue js and laravel. I tried to add a value to the href attribute using laravel resource and pass an id as a second parameter but when I click on it and display the id on console it shows the same id to all data which is incorrect.
Sample blade:
<a id="deleteRecord" data-id="{{$project->id}}" #click.prevent="deleteRecord" class="btn btn-circle btn-icon-only btn-danger" href="{{ route('projects.store', $project->id) }}">
<i class="icon-trash"></i>
</a>
Vue method:
deleteRecord: function(id) {
var dataId = $('#deleteRecord').attr('href')
console.log(dataId);
}
Inside of your method deleteRecord:
this.$http.delete(url)
.success(function(response) {
console.log(response)
})
.error(function(errors) {
console.log(error)
});
Or using axios inside of your method
axios.delete(url)
.then(function (response) {
console.log(response);
})
.catch(function (error) {
console.log(error);
});
I get it now. I'll just post it here to help others also.
deleteRecord: function(id) {
var url = "projects" + "/" + id;
$.ajax({
url: url,
type: 'DELETE',
data: { "_token": "{{ csrf_token() }}" },
success: function(response) {
//do some success stuff here..
}
});
}

Ajax Mandrill send email on form submit

If i go through this code step by step in firebug it works, but it wont work on button press. Using a button outside form to call it works ok.....It seems that complete: line does not get executed at all.
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
<script type="text/javascript">
$(document).ready(function() {
$('#salji').click(function() {
var testing = false;
$.ajax({
type: "POST",
url: "https://mandrillapp.com/api/1.0/messages/send.json",
data: {
'key': 'Vv_8cJDX9*****',
'message': {
'from_email': 's****#gmail.com',
'to': [
{
'email': 'd*****#gmail.com',
'name': 'Test',
'type': 'to'
}
],
'autotext': 'true',
'subject': 'New subject',
'html': 'YOUR EMAIL CONTENT HERE! YOU CAN USE HTML!'
}
},
complete: function() {
testing = true;
$('#forma').attr('action', 'http://%SERVERIP%/signup1?%PARAMS%');
$('#forma').submit();
}
}
)
})
})
</script>
<div class="form1" align="center"><input class="button" value="#CONTINUE#" name="signup" type="submit" id="salji">
You don't have a form on the page, therefore .submit won't do anything.
Change your DIV to a form and give it the correct ID, "form1" is a class atm.

Resources