Knockout onClick with dynamic data as parameter - javascript

I have an addToCart function I'd like to utilize that is NOT in my viewModel.
In my view model I have an object {"items":[ {"name":"siren","id":2,"image":"s3.amazon.com"}])
And in my knockout app:
<div id="cart" class="shopify-buy__cart-scroll">
<div class="shopify-buy__cart-items" data-bind="foreach: newcart.items">
<div class="shopify-buy__cart-item">
<div data-bind="style: { 'background-image': 'url(' + images + ')'}" class="shopify-buy__cart-item__image" alt="Product" style="background-repeat:no-repeat;background-size: contain;"></div>
<span class="shopify-buy__cart-item__title" data-bind="text: name"></span>
<span class="shopify-buy__cart-item__price" data-bind="text: price "></span>
<div class="shopify-buy__quantity-container">
<button class="shopify-buy__btn--seamless shopify-buy__quantity-decrement" type="button">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M4 7h8v2H4z"></path></svg>
</button>
<input class="shopify-buy__quantity shopify-buy__cart-item__quantity-input" type="number" min="0" aria-label="Quantity" data-bind="attr: {value: quantity}" style="height: 30px; border:solid 1px #d3dbe2 !important;padding-left:13px;" />
<button class="shopify-buy__btn--seamless shopify-buy__quantity-increment" type="button" databind="click: addToCard(id)" >
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path d="M12 7H9V4H7v3H4v2h3v3h2V9h3z"></path></svg>
</button>
</div>
</div>
</div>
<div class="shopify-buy__cart-bottom">
<p class="shopify-buy__cart__subtotal__text" >SUBTOTAL</p>
<p class="shopify-buy__cart__subtotal__price"data-bind="text: total"></p>
<p class="shopify-buy__cart__notice">Shipping and discount codes are added at checkout.</p>
<button class="shopify-buy__btn shopify-buy__btn--cart-checkout" type="button">CHECKOUT</button>
</div>
</div>
In this line: <button class="shopify-buy__btn--seamless shopify-buy__quantity-decrement" type="button">
I want to add some a function with a dynamic parameter.
<button class="shopify-buy__btn--seamless shopify-buy__quantity-incriment" type="button" databind="attr: {onclick:addToCart( id ) }">
But this doesn't work. How can I do this without adding the function to my viewmodel. It's only an object and I'd like to keep it that way.
Thanks!

how about using the html5 data attribute. to get your knockout id into your click function.
data-bind="attr: {'data-idfromko': id }"
run snippet below.
function model() {
var self = this;
this.theArray = ko.observableArray([{
'id': 1,
'foo': 'bar'
}, {
'id': 2,
'foo': 'bar2'
}, {
'id': 3,
'foo': 'bar3'
}, ]);
}
var mymodel = new model();
$(document).ready(function() {
ko.applyBindings(mymodel);
$('.button').click(function(event) {
value = $(this).data("idfromko" )
alert(value);
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<table>
<thead>
<tr>
<th>id</th>
<th>foo</th>
<th></th>
</tr>
<</thead>
<tbody data-bind=foreach:theArray>
<tr>
<td data-bind="text:id"></td>
<td data-bind="text:foo"></td>
<td>
<button class="button"
data-bind="attr: {'data-idfromko': id }">
click
</button>
</td>
</tr>
</tbody>
</table>

Related

Vee validate v3 ValidationObserver not working with dynamic validation provider added using v-for

I am using Vee Validate v3 for validating dynamic input generated using V-FOR, which is added or removed input elements based on user actions.
My issue was only the last input gets validated other inputs not getting validated. In the documentation, they had mentioned this issue while using V-IF & V-FOR
documentation link
They told to use VueJS keep-alive component. but not working with V-FOR.
<validation-observer ref="observer" v-slot="{ handleSubmit }">
<form method="POST" action="{{ route('app.store') }}" #submit.prevent="handleSubmit(onSubmit)" class="form" enctype="multipart/form-data">
<table class="table">
<thead>
<tr>
<th>SI No</th>
<th>input 1</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in items" :key="item.id">
<td>#{{ index + 1 }}</td>
<td>
<keep-alive>
<validation-provider rules="required" v-slot="{ errors }" name="attribute">
<div class="form-group">
<input :name="'attribute' + item.id" class="form-control" v-model="item.attribute">
<span class="error" role="alert">
#{{ errors[0] }}
</span>
</div>
</validation-provider>
</keep-alive>
</td>
<td>
<button type="button" class="btn btn-md btn-danger mt-4" #click="remove(index)">
<span class="ion-trash-a"></span>
</button>
</td>
</tr>
</tbody>
</table>
<x-form-submit>Save</x-form-submit>
</form>
My js code
<script type="application/javascript">
Vue.component('dynamic-form-wrapper', {
template: '#dynamic-form-template',
data() {
return {
items: [
{
id: 1,
attribute: null,
},
{
id: 2,
attribute: null,
}
],
id: 3
}
},
methods: {
async onSubmit() {
const valid = await this.$refs.observer.validate();
if (valid) {
document.getElementById("category-form").submit();
}
},
add() {
this.items.push({
id: this.id,
attribute: null,
});
this.id ++;
},
remove(index) {
if (this.items.length != 1) {
this.items.splice(index, 1);
}
}
}
})
</script>
Thanks in advance
Each ValdationProvider needs a unique id. set :vid for each validation providers
<keep-alive>
<validation-provider :vid="'attribute' + item.id" rules="required"
v-slot="{ errors }" name="attribute">
<x-v-form-input type="text" v-model="item.attribute" field="attribute">
</x-v-form-input>
</validation-provider>
</keep-alive>
Refer API docs for vid here: https://vee-validate.logaretm.com/v3/api/validation-provider.html#props

How can I get specific value from returned response

I'm trying to create shopping cart with laravel. I have a little problem to update cart items price onchange quantity number input. Ajax sends value and returns in array. But this time the problem is beginning.
This is my cart blade:
#extends('user.addons.app')
#push('customCss')
<link rel="stylesheet" href="
{{asset('/user/assets/css/numberinput.css')}}">
#endpush
#section('content')
#include('user.modules.header')
#include('user.modules.lsidebar')
<div id="cart-page-wrapper" class="pt-86 pt-md-56 pt-sm-46 pb-50 pb-md-20
pb-sm-10">
<div class="container">
<div class="row">
<div class="col-lg-8">
<div class="shopping-cart-list-area">
#include('user.modules.cart_data')
<div class="cart-coupon-update-area d-sm-flex justify-
content-between align-items-center">
<div class="coupon-form-wrap">
<form action="#" method="post">
<input type="text" placeholder="Coupon
Code"/>
<button class="btn-apply">Apply
Button</button>
</form>
</div>
<div class="cart-update-buttons mt-xs-14">
<button class="btn-clear-cart">Clear
Cart</button>
<button class="btn-update-cart">Update
Cart</button>
</div>
</div>
</div>
</div>
<div class="col-lg-4">
<!-- Cart Calculate Area -->
#include('user.modules.cartcalculate')
</div>
</div>
</div>
</div>
#include('user.modules.footer')
#endsection
#push('customJs')
#endpush
This is my cart_data blade:
<div class="shopping-cart-table table-responsive">
<table class="table table-bordered text-center" >
<thead>
<tr>
<th>Products</th>
<th>Price</th>
<th>Quantity</th>
<th>Total</th>
</tr>
</thead>
<tbody>
#foreach($carts as $cart)
<tr>
<td class="product-list">
<div class="cart-product-item d-flex align-items-center">
<div class="remove-icon">
<button><i class="fa fa-trash-o"></i></button>
</div>
<a href="single-product-sticky.html" class="product-
thumb">
<img src="{{asset($cart['image'])}}" alt="Product"/>
</a>
<a href="single-product-tab-left.html" class="product-
name">{{$cart['title']}}</a>
</div>
</td>
<td>
<span class="price">$ {{$cart['price']}}</span>
</td>
<td>
<input type="hidden" value="{{$cart['id']}}"
id="mon{{$cart['id']}}">
<div class="quantity">
<input type="number" min="1" max="9" step="1" value="
{{$cart['quantity']}}" id="qty{{$cart['id']}}">
</div>
</td>
<td>
<span class="price" id="toss{{$cart['id']}}">
{{$cart['price'] * $cart['quantity']}}
</span>
</td>
</tr>
#endforeach
</tbody>
</table>
</div>
<script src="{{asset('/assets/plugins/jquery/jquery.min.js')}}">
</script>
<script src="{{asset('/user/assets/js/numberinput.js')}}"></script>
<script>
#foreach($carts as $cart)
$("#qty{{$cart['id']}}").change(function(e){
e.preventDefault();
var value = $("#qty{{$cart['id']}}").val();
var id = $("#mon{{$cart['id']}}").val();
var inputQuantityElement = $("#x{{$cart['id']}}");
$.ajax({
type: 'post',
url: '/cartupdate',
data: {_token: '{{ csrf_token() }}',value:value, id:id},
success : function(response) {
$(inputQuantityElement).val(response);
}
});
});
#endforeach
</script>
And this is my function in controller:
public function cartupdate(Request $request)
{
$cartss = Cart::find($request['id']);
$quantity = $cartss->quantity;
if($quantity < $request['value'])
{
$update = $cartss->quantity+1;
}
else if($quantity > $request['value'])
{
$update = $cartss->quantity-1;
}
else
{
die;
}
$cartss->update(['quantity' => $update]);
$carts = Cart::where('user_id', Auth::user()->id)->get();
foreach ($carts as $cart)
{
$id[] = $cart['id'];
$pric[] = $cart['price'] * $cart['quantity'];
}
return $pric;
}
I want to change dynamicly prices when user clicked quantity input
In your cartupdate() function, return $pric won't give you the result because $pric is declared inside foreach(). Change it to:
$pric = 0;
foreach ($carts as $cart) {
$pric += $cart['price'] * $cart['quantity'];
}
return $pric;
will give you the total. But, I guess you are trying to get a new price for a particular cart. If so, change your cartupdate() function to:
public function cardupdate(Request $request) {
$cart = Cart::find($request['id']);
$quantity = $cart->quantity;
if($quantity < $request['value'])
{
$quantity++;
}
else if($quantity > $request['value']){
$quantity--;
}
$cart->update(['quantity' => $quantity]);
return $quantity*$cart->price;
}
To update the price in the view, you can use
document.getElementById("toss{{$cart['id']}}").innerHTML = response;
Personally, I suggest you to try Vue.js to build your application. Laravel + Vue.js is a good combination. It much easier to build complex application like this.
Sorry for my bad english :)

Only the first row row is required field - Dynamic table Knockout.js

<table style="margin-left:20px" border="0" class="ledgergrid" width="" cellpadding="1" cellspacing="2">
<tbody>
<tr>
<th> </th>
<th>A. Citation</th>
<th>B. Relevant Finding(s)</th>
<th>C. Overlap of Populations and/or Settings</th>
</tr>
<tbody data-bind="foreach: listCitations">
<tr style="background-color:#eeeeee;" >
<td width="1%">
<button type="button" class="btn-xs" id="delete" data-bind="click: $parent.removeCitation.bind($data), attr: { title: $parent.deleteCitationToolTip }, visible: $parent.lastIndexCitations() > 2"> X </button>
<td width="33%" ><textarea name="citation" class="" style="width:80% !important; height:60px;" data-bind="css: $parent.KCitationCSS, attr: {id: 'citation' + ($index() + 1), title: $parent.citationToolTip}, value: $data.KCitation"></textarea></td>
<td width="33%"><textarea name="relevantFindings" class="" style="width:80% !important; height:60px;" data-bind=" css: $parent.relevantFindingsCSS, attr: {id: 'relevantFindings' + ($index() + 1), title: $parent.relevantFindingsToolTip}, value: $data.KRelevantFindings"></textarea></td>
<td width="33%"><textarea name="overlapofPopulations" class="" style="width:80% !important; height:60px;" data-bind=" css: $parent.overlapofPopulationsCSS, attr: {id: 'overlapofPopulations' + ($index() + 1), title: $parent.overlapofPopulationsToolTip}, value: $data.KOverlapofPopulations"></textarea></td>
</tr>
</tbody>
</table>
I have displayed 4 rows in default case, but all of them showed as required. I would like to have only the first row required, and the rest are not required. FYI all the table is dynamic! Thanks in advance!
Do you mean to say first column after the button? From the code shared, I can see only 1 row. Am assuming that its 1st column (A. Citation) thats required and needs special look & feel. If yes, here is CSS code:
.ledgergrid td:nth-child(2) {
background-color:red;
}

KnockoutJS: Making sure items are removing before adding new ones

I'm running into an issue where ever so often my data isn't being updated properly in my model. I've create a fiddle for it here: https://jsfiddle.net/wzhv5ghy/
In the code all the updates to the model are handled by this function:
self.updateData = function (data) {
self.deliveryItems.removeAll(); // Sometimes our data won't replace correctly so lets kill everything here and then add it from scratch
if ( data != null )
self.deliveryItems(data);
self.showLoadingNotice(false);
// let the sorting plugin know that we made a update
$("#deliveryItemsTable").trigger("update");
//self.showTable(true);
//console.log(data);
// We moved this to functions.js since we use it multiple times
//setupDeliveryItemsTable();
};
I put it in a single function to make it easier when updating the code using various JSON calls.
The issue thats happening is that every so often the page will show old data (say 1000 lines) in the table while the following code will display 1 as the count of the models data:
<span data-bind="text: deliveryItems().length"></span>
My guess is that this line:
self.deliveryItems.removeAll();
is not running before this line
self.deliveryItems(data);
How can I make sure that all items are removed from the model and table before the model is updated with the fresh data?
Like #f_martinez pointed out our issue was with the jQuery Tablesorter plugin and KO fighting to update the same table. To get around this I ended up using the code below to switch to a KO tablesorting function. The sorting seems to be slower than the Tablesorter version but it's better than having bad data.
/*-------------------------------------------------------------------------*/
// Knockout.js Functions
/*-------------------------------------------------------------------------*/
ko.bindingHandlers.sort = {
init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var asc = false;
element.style.cursor = 'pointer';
//console.log(element);
element.onclick = function(){
var value = valueAccessor();
var prop = value.prop;
var data = value.arr;
asc = !asc;
data.sort(function(left, right){
var rec1 = left;
var rec2 = right;
if(!asc) {
rec1 = right;
rec2 = left;
}
var props = prop.split('.');
for(var i in props){
var propName = props[i];
var parenIndex = propName.indexOf('()');
if(parenIndex > 0){
propName = propName.substring(0, parenIndex);
rec1 = rec1[propName]();
rec2 = rec2[propName]();
} else {
rec1 = rec1[propName];
rec2 = rec2[propName];
}
}
return rec1 == rec2 ? 0 : rec1 < rec2 ? -1 : 1;
});
};
}
};
Here's the code for the table itself:
<div class="alert alert-info" data-bind="visible: showLoadingNotice">
Loading...
</div>
<table class="table table-striped table-bordered table-hover" id="deliveryItemsTable" data-bind="visible: showTable">
<thead>
<tr class="title1">
<td colspan="100%">Delivery Items ( <span data-bind="text: deliveryItems().length"></span> )</td>
</tr>
<tr class="title2">
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'created_on' }">Created On</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'company_name' }">Company</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'product_number_company' }">Item Number</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'client_phone_number' }">Phone Number</th>
<th data-bind="sort: { arr: deliveryItems, prop: 'price_to_display' }">Price</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'location' }">Location</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'notes' }">Notes</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'delivered_by_text' }">Delivery Driver</th>
<th class="header" data-bind="sort: { arr: deliveryItems, prop: 'status_labels' }">Status</th>
<th class="header nowrap"></th>
</tr>
</thead>
<tbody data-bind="foreach: deliveryItems">
<tr data-bind="attr: {id: 'row_' + id }">
<td data-bind="text: created_on"></td>
<td data-bind="text: company_name"></td>
<td data-bind="text: product_number_company"></td>
<td data-bind="text: client_phone_number"></td>
<td data-bind="text: formatCurrency(price_to_display)"></td>
<td>
<span data-bind="text: location, attr: {id: 'edit-delivery_items-' + id + '_location' }"></span>
<button data-bind="click: $root.editLocation, visible: $root.canEdit" class="btn btn-default btn-xs" title="Edit"><i class="glyphicon glyphicon-edit"></i></button>
</td>
<td>
<span data-bind="text: notes, attr: {id: 'edit-delivery_items-' + id + '_notes' }"></span>
<button data-bind="click: $root.editNotes, visible: $root.canEdit" class="btn btn-default btn-xs" title="Edit"><i class="glyphicon glyphicon-edit"></i></button>
</td>
<td>
<span data-bind="text: delivered_by_text, attr: {id: 'edit_select-delivery_items-' + id + '_delivered_by' }"></span>
<button data-bind="click: $root.editDeliveryDriver, visible: $root.canEdit" class="btn btn-default btn-xs" title="Edit"><i class="glyphicon glyphicon-edit"></i></button>
</td>
<td data-bind="html: status_labels"></td>
<td class="center"><span class="btn-group">
<button data-bind="click: $root.markAsDelivered, visible: $root.canMarkComplete" class="btn btn-default" title="Mark as delivered"><i class="glyphicon glyphicon-check"></i></button>
<a data-bind="attr: {href: 'index.php?action=editDeliveryItem&id=' + id }, visible: $root.canEdit" class="btn btn-default" title="Edit"><i class="glyphicon glyphicon-edit"></i></a>
</span></td>
</tr>
</tbody>
</table>
<div class="alert alert-info" data-bind="visible: showLoadingNotice">
Loading...
</div>
<div id="deliveryItemsTableUpdateNotice"></div>

Passing calculated value to ng-model

I am new to AngularJS and am trying to set up a simple Orders form. I'm having problems passing the id value to the controller. I want it to be the last order id + 1 so it acts as an identity field. Ideally I'd like it to be hidden, but if I could just get this part working that would help.
Here is what I have tried:
HTML:
<div ng-controller = "OrdersCtrl">
<h1>Orders</h1>
<form class="form-inline" ng-submit="addOrder()">
<strong>Add order: </strong>
<input value="{{orders[orders.length - 1].id + 1 }}" ng-model="newOrder.id" >
<input type="number" step="0.01" class="form-control" placeholder="Total" ng-model="newOrder.total">
<input type="number" class="form-control" ng-model="newOrder.product_id">
<input type="submit" value="+" class="btn btn-success">
</form>
<table class="table table-hover">
<thead>
<td>Order ID</td>
<td>Total</td>
<td>Product</td>
<td></td>
</thead>
<tr ng-repeat="order in orders | orderBy: '-id':reverse">
<td>
{{order.id}}
</td>
<td>
<strong>{{order.total | currency}}</strong>
</td>
<td>
{{order.product_id}}
<small ng-show="order.user_id"><br>-{{order.user_id}}</small>
</td>
<td>
<button ng-click="deleteOrder(order)" class="btn btn-danger btn-sm"><span class="gylphicon glyphicon-trash" aria-hidden="true"></span></button>
</td>
</tr>
</table>
</div>
JS:
var app = angular.module('shop', []);
$(document).on('ready page:load', function() {
angular.bootstrap(document.body, ['shop'])
});
app.controller('OrdersCtrl', ['$scope', function($scope){
$scope.orders = [
{id: 1, total: 55, product_id: 5, user_id: 1},
{id: 2, total: 33, product_id: 3, user_id: 1},
{id: 3, total: 51, product_id: 12, user_id: 1}
];
$scope.addOrder = function(){
if(!$scope.newOrder.product_id || $scope.newOrder.total === ''){return;}
$scope.orders.push($scope.newOrder);
};
$scope.delOrder = function(order){
$scope.orders.splice($scope.orders.indexOf(order), 1);
};
}]);
I'm not getting any errors but nothing is appearing in the id column. Any advice would be appreciated.
Since $scope keeps all the existing orders, you shouldn't need to pass in a new id. Just calculate it in the controller when adding a new order.
remove this line from the view
<input value="{{orders[orders.length - 1].id + 1 }}" ng-model="newOrder.id" >
In controller
$scope.newOrder = {};
$scope.addOrder = function(){
if($scope.newOrder.total === ''){ return; }
$scope.newOrder.id = $scope.orders[$scope.orders.length - 1].id + 1
$scope.orders.push($scope.newOrder);
$scope.newOrder = {};
};

Categories

Resources