Add class to bootstrap textbox - yii2 - yii2

I have a form in yii2. In that I have a input field like below.
<input type="text" class="form-control" id="productsales-<?= $i ?>-value">
It's inside a dynamic form andno coming from any model.It's just to hold some calculated value. And the idea is to calculate the values fileed by users in this field to another textbox like below -
<?= $form->field($model, 'amount')->textInput(['maxlength' => true, 'class' => 'sum']) ?>
I already have the javascript code to calculate the total amount like below -
<?php
/* start getting the total amount */
$script = <<<EOD
var getSum = function() {
var items = $(".item");
var sum = 0;
items.each(function (index, elem) {
var priceValue = $(elem).find(".sumPart").val();
//Check if priceValue is numeric or something like that
sum = (parseFloat(sum) + parseFloat(priceValue)).toFixed(2);
});
//Assign the sum value to the field
$(".sum").val(sum);
};
//Bind new elements to support the function too
$(".container-items").on("change", ".sumPart", function() {
getSum();
});
EOD;
$this->registerJs($script);
/*end getting the total amount */
?>
Please note that I have a class sum in the second textbox which comes from a model. My question is that how can I add a class(sumPart) like this(sum) in the first textbox which is not coming from a model.
Update after discussion with Insane Skull
I have a dynamic form like below
I want to calculate sum of all the entries in Value and pass it to amount.
The code of Value is -
<input type="text" class="form-control sumPart" id="productsales-<?= $i ?>-value">
The code of Amount is -
<?= $form->field($model, 'amount')->textInput(['maxlength' => true, 'class' => 'sum']) ?>
The javascript I've tried is already given above.
The problem with this code is the calculated "value" is not passing to amount textbox.
Update: A little issue

Modify JS :
$this->registerJs('
function getSum() {
var sum = 0;
var items = $(".item");
items.each(function (index, elem) {
var priceValue = $(elem).find(".sumPart").val();
sum = parseFloat(sum) + parseFloat(priceValue);
});
if(isNaN(sum) || sum.length == 0) {
sum = 0;
}
$(".sum").val(sum);
}
$(".container-items").on("change", function() {
getSum();
});
jQuery(".dynamicform_wrapper").on("afterDelete", function(e) {
getSum();
});
');

I am little unsure about the problem you are facing. If you want to add a class to a input field, you can easily do so in the class attribute of the input tag. And the class attribute does support multiple values, so you can write something like this:
<input type="text" class="form-control sumPart" id="productsales-<?= $i ?>-value">
**UPDATE -- based on comment by OP **
#Tanmay, I understand that you may not be getting the desired output. However, it should not really matter if we are rendering the fields using $form->field() or plain HTML. If the fields have the proper classes assigned, then the JS code posted by Kostas in your referenced link should work - irrespective of how the class name was added to the input field.
Can you please confirm (by using Inspect Element) if the sumPart class is actually assigned to the relevant fields once the page loads? Maybe some JS is overwriting the class attribute again?
If the classes are assigned properly, please check if there are any other JS errors in console which may prevent the sum calculation JS from working properly.

Related

Using DataTables how to display a running total of an amount entered in each row?

http://live.datatables.net/dalogaci/1/edit
I have an amount of money to be dispersed and am using DataTables to display a list of people and allow entry of an amount next to each person (their share of the disbursement). I want to provide a running total of the amount entered into the table so I can warn when the total to be dispersed has been reached or passed.
Kind regards,
Glyn
You can use the following approach.
In my case, I display the running total in a <div>, rather than an input box, as the value is only for display purposes:
<div id="showsum">Grand Total: $0.00</div>
The end result:
The script for this - which I have tried to explain with comments in the code:
<script type="text/javascript">
// define the table variable here so the doSum()
// function will have access to it, when needed:
var table;
// reads each value from the final column in the table, checks
// if the value is a number (as opposed to blank), and then
// keeps a running total. Ensure we round fractions of pennies
// as needed.
//
// When handling money, use a big number library - see this:
// https://stackoverflow.com/questions/1458633/how-to-deal-with-floating-point-number-precision-in-javascript
//
function doSum() {
//var foop = table.columns(5).nodes().to$();
var sum = 0.0;
// this gets each node (cell) in the final column:
table.columns(5).nodes().to$()[0].forEach(function (item) {
// see if the display value is a number (i.e. not blank):
var amt = parseFloat($('input', item ).val());
if (!isNaN(amt)) {
sum += amt;
}
});
// round and display to 2 decimal places:
sum = (Math.round((sum + Number.EPSILON) * 100) / 100).toFixed(2);
$('#showsum').text("Grand Total: $" + sum);
}
$(document).ready(function() {
table = $('#example').DataTable( {
"columnDefs": [ {
"targets": 5,
"data": function ( row, type, val, meta ) {
// note the use of onchange="doSum()" in the following:
return '<input type="number" min="0" max="99999.99" step=".01" placeholder="0.00" onchange="doSum()">';
}
} ]
} );
} );
</script>
For a change to be added to the grand total, you have to hit "enter", or click outside of the input field, if you type the value in manually.
Because you are dealing with money, the code should really be using a "big number" format to eliminate the risk of inaccuracies in fractions of pennies (due to limitations in floating point arithmetic). For example see here.
Final note: I see this question was down-voted. I think that may have been because you only link to your demo code, instead of showing the relevant parts in the question itself. The link to the demo is useful - but showing code in the question itself is generally a "must-do", I think.

Svelte virtual list component - function not working after filtering list

I am using the virtuallist component in a svelte project. I have added filtering to the list. My issue is that a function in my project stops working when I filter the list, I'm assuming because the list item is not yet in the dom when filtered?
The project converts medical units from metric units to international units using two inputs. Changing one input automatically converts the other.
Before filtering, everything works well with conversion but after entering a item name, (e.g. Type Zinc), the input conversion fails in the filtered items. No conversion occurs.
I've looked into afterUpdate as an option but not sure how to implement it.
---------Added Info -------------------
The issue is with list items not yet in view. Try typing "zinc" and then changing the input values of Zinc (fails) vs typing Acetone (item already in view) and changing those inputs (it works).
Here is a working REPL
The script:
<script>
import VirtualList from './VirtualList.svelte';
import unitsH from './data.js';
let searchTerm = "";
let start;
let end;
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm) !== -1);
function setBothFromSIH(value, i) {
const {factor, siValue} = unitsH[i];
unitsH[i].siValue = +value;
unitsH[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = unitsH[i];
unitsH[i].usValue = +value;
unitsH[i].siValue = +(value * factor).toFixed(2);
}
</script>
With simplified html code:
<VirtualList items={filteredList} bind:start bind:end let:item >
<div class="border" style="overflow-x: scroll;"> <div><div>
<div class="name">{item.name}</div>
<span>Specimen: {item.specimen} </span>
<span> Conversion Factor: {item.factor} </span>
</div>
<div>
<label>US Range:{item.conventionalRange} {item.conventionalUnit}</label>
<input name="us{filteredList.indexOf(item)}" value={item.usValue} on:input="{e => setBothFromUSH(e.target.value, filteredList.indexOf(item))}" type=number placeholder=" US">
</div>
<div>
<label>SI Range: {item.siRange} {item.siUnit}</label>
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, filteredList.indexOf(item))}" type=number placeholder="SI">
</div></div> </div>
</VirtualList>
<p>showing items {start}-{end}</p>
Thanks for any help in getting this to work!
It's a small issue with your filter. You convert the product name to lower case but not the filter term ;) If you enter acetone instead of Acetone, then it works. The fix:
$: filteredList = unitsH.filter(item => item.name.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1);
Edit:
The issue with not calling the function for some filtered element is that you display the filteredList but still do the lookup on the unitsH list. Change it to this and it works:
function setBothFromSIH(value, i) {
const {factor, siValue} = filteredList[i];
filteredList[i].siValue = +value;
filteredList[i].usValue = +(value / factor).toFixed(2);
}
function setBothFromUSH(value, i) {
const {factor, usValue} = filteredList[i];
filteredList[i].usValue = +value;
filteredList[i].siValue = +(value * factor).toFixed(2);
}
Happy hacking!
Your problem is caused by using the wrong index, in the change handler you pass the index of the item in filteredIndex but then you use that one to change the item on that index in the array unitsH.
You can see that by:
- start anew
- note the value for Acetaminophen (index 0)
- search zinc
- change value of zinc (index 0 in filtered list)
- clear search
->> acetaminophen has changed because that is index 0 in unitsH
You can easily solve this by passing in the index of the original array instead:
<input name="si{filteredList.indexOf(item)}" value={item.siValue} on:input="{e => setBothFromSIH(e.target.value, unitsH.indexOf(item))}" type=number placeholder="SI">
However, if you move the markup for each item to a seperate component you can vastly simplify this by directly interacting with the properties instead of trying to change them in the array.

Adding two javascript functions(depending on input)

I have put together a calculator what calculates a price, depending on user input. It works fine with one input, but now I have to scale it a little with a second user input. But here's the catch: the user might not want to put anything to the field, so it will be empty. And that's the thing that brakes my code. I could duplicate the calculator function and return the values and add those two together in a third function, but it will not work when there's an empty value.
Just for the sake of it, some trivial HTML code:
//When I only calculate with this user input, its easy
<input type="text" id="rocktext"><br>
// But how to consider this and do the same exact calculations like with the
//first one and add those two result together?
<input type="text" id="rocktext2"><br>
The code in the end should look like:
Take first user input, calculate the price(like in code below)
IF(!!) there is a second user input, calculate the price and add it to
the first one
Am I being a moron to try it with JS or just a moron in the firstplace?
Hope to hear from You, guys!
J.
The initial JS code is as follows:
function priceCalc() {
var inputs = document.getElementById("rocktext").value;
var length = inputs.length;
var accept = 6;
var initPrice = 8;
if (inputs<=accept){
// Since the code is much simpler right now i just put the result in HTML as follows:
document.getElementById("rockpricetotal").innerHTML = initPrice + " dollars";
//I can also return the the value calculated here like so:
//retVal = initPrice;
}
else {
var intLength = parseInt(length, 10);
var lengthGap = intLength - accept;
var totals = lengthGap * 0.8 + initPrice;
var prec = totals.toPrecision(3);
// Since the code is much simpler right now i just put the result in HTML as follows:
document.getElementById("rockpricetotal").innerHTML = prec + " dollars";
// Here also the return clause can be possible with the calculation result like so:
//retVal = prec;
}
// And the final return as an alternative to the innerHTML :
// return retVal;
}
Making it scalable, you can add a class to all the inputs which may be in the function (something like calcInput), so you iterate all of them and if the value isn't empty (and if it's a valid number), you put it in the calculation.
Or you can just verify if the second input is empty, if so, calls functionOne, if not, calls functionTwo:
function twoDifferentWays() {
var valueOne = document.querySelector("#rocktext").value;
var valueTwo = document.querySelector("#rocktext2").value;
if (!!valueTwo && !isNaN(valueTwo)) {
callsFunctionOne(valueOne, valueTwo);
} else {
callsFunctionTwo(valueOne, valueTwo);
}
}

How to filter or custom filter array of objects based on matching values from another object

I implemented an advance search with 15 input fields in AngularJS.
In the page load itself the result set is return from database in JSON format and i need to do the filter in client side only.
The input criteria's equivalent column is available in the result set and i need to check in its respective column only.
I am converting each column by JSON.stringify() and check with the search params like the below :
$scope.filteredData = $scope.actualData.filter(function(item) {
return JSON.stringify(item.FirstName).toLowerCase().indexOf(lowerFirstName) != -1 &&
JSON.stringify(item.LastName).toLowerCase().indexOf(lowerLastName) != -1 &&
JSON.stringify(item.EmailAddress).toLowerCase().indexOf(lowerEmailAddress) != -1 &&
JSON.stringify(item.Address1).toLowerCase().indexOf(lowerAddress1) != -1 &&
JSON.stringify(item.Address2).toLowerCase().indexOf(lowerAddress2) != -1;
...... etc // upto 15 fields
});
Since i have the 15 input fields and the actual result set contains a minimum of 50,000 records.
So converting each record's each column by JSON.stringify() and check with search params will surely cause the performance issue.
Is there any other way to achieve the filtering in client side with other approach.
I posted a sample code in Plunker with 5 input fields only : http://plnkr.co/edit/nUWZEbGvz7HG6gb91YZP
sylwester's answer is the normal way you'd filter things. Your code looks like you want to filter down to only the object that matches every input field. You code attempts to find an object where every property matches the searchParams object. At that point, I don't see what benefit there is to finding that object, because the user already created the object again! Nonetheless, here's a proper version of your code:
Live demo here.
<div ng-repeat="data in actualData | filter:searchData()">
$scope.searchData = function() {
return function(item) {
return Object.keys(item).every(function(key) {
// skip the $$hashKey property Angular adds to objects
if (key === '$$hashKey') { return true; }
var searchKey = key.charAt(0).toLowerCase()+key.slice(1);
return item[key].toLowerCase() === $scope.searchParams[searchKey].toLowerCase();
});
};
};
You really need to limit the data coming from the server for the browser's sake and for the server's sake. It's easy to implement a LIMIT, OFFSET system. It sounds like, overall, you just need to be able to query the server for a certain record.
From your comments, it seems you definitely want Angular's built in filter filter:searchParams, and just capitalize your searchParams models to match your data. For fun, I'll include more options for finer tuning.
This one almost mimics filter:searchParams. You can change > 1 to adjust when the partial matching kicks in, or have it return true only when both items are strictly equal === to disable partial matching. The difference here is that all items are hidden until matched, whereas filter:searchParams will show all items and then remove what doesn't match.
Live demo here.
$scope.searchData = function() {
return function(item) {
return Object.keys(item).some(function(key) {
if (key === '$$hashKey') { return false; }
var searchKey = key.charAt(0).toLowerCase()+key.slice(1);
var currentVal = $scope.searchParams[searchKey].toLowerCase();
var match = item[key].toLowerCase().match(currentVal);
return currentVal.length > 1 && match;
});
};
};
Lastly, to perfectly mimic filter:searchParams, you'd just put in a check to NOT filter the items until there is user input and the input is long enough to start the partial match.
Live demo here.
$scope.searchData = function() {
var partialMatchLength = 2;
return function(item) {
var shouldFilter = Object.keys($scope.searchParams).some(function(key) {
return $scope.searchParams[key] && $scope.searchParams[key].length >= partialMatchLength;
});
if (!shouldFilter) { return true; }
return Object.keys(item).some(function(key) {
if (key === '$$hashKey') { return false; }
var searchKey = key.charAt(0).toLowerCase()+key.slice(1);
var currentVal = $scope.searchParams[searchKey].toLowerCase();
var match = item[key].toLowerCase().match(currentVal);
return currentVal.length >= partialMatchLength && match;
});
};
};
First of all you ng-repeter with 50.000 records more likely is going to kill your browser, so you should thing about pagination.
Secondly you can easy filter your data using angular filter please see that demo
http://plnkr.co/edit/R8b8G4xCMSQmX1144UJG?p=preview
<div ng-controller="ListCtrl">
<br />
First Name:
<input type="text" id="txtFirstname" ng-model="searchParams.FirstName">
<br/>Last Name:
<input type="text" id="txtLastname" ng-model="searchParams.LastName">
<br/>Email Address:
<input type="text" id="txtEmailAddress" ng-model="searchParams.EmailAddress">
<br/>Address 1:
<input type="text" id="txtAddress1" ng-model="searchParams.Address1">
<br/>Address 2:
<input type="text" id="txtAddress2" ng-model="searchParams.Address2">
<br/>
<button class="btn btn-primary" ng-click="searchData()">Search</button>
<br />
<hr />
<b>Filtered Data(s):</b>
<div ng-repeat="data in actualData | filter:searchParams ">
<span ng-bind="data.FirstName"></span>
<span ng-bind="data.LastName"></span> |
Address : {{data.Address1}}
</div>
<hr />
</div>

How to maintain checkbox value through pagination?

I have a list of products which are shown with pagination and I can filter the display of the products using check boxes. The problem is it can only displays the value from check box at first page, and if i go to the next page, i will lose the check box checked's value. Please help me how to solve this. I don't know where should I put input hidden and how to write Java Script. Here's my code:
<input id="checkbox_brand" type="checkbox" name="checkbox_brand[<? echo $data_brand[brand_name]; ?>]" value="<? echo $data_brand[brand_name]; ?>"/>
if (isset($_POST["checkbox_brand"])){
foreach($_POST["checkbox_brand"] as $status_a) {
$status_sql[] = '\''.$status_a.'\'';
}
$status = implode(',',$status_sql);
session_start();
$_SESSION["selected"]=$status;
}
if (session_is_registered("selected")){
-->my query
}
Each time you press a check box you'll have to use dictionary array in JavaScript
For example:
if I use the example page you gave, then when pressing a check box inside "Categories"
you'll have to put a value inside correct variable.
// Initialize Objects
var userChoice = {};
userChoice.Catagories = {};
userChoice.Brands = {};
userChoice.ScreenSize = {};
.
.
.
// Pressing check box will trigger the bellow
userChoice.Categories["LG"] = 0;
userChoice.Categories["Sharp"] = 1;
userChoice.Categories["Sony"] = 1;
userChoice.Brands["LcdTV"] = 1;
userChoice.Brands["LedTV"] = 0;
Each checkbox press will also trigger the following JavaScript
document.getElementById("userChoiceHiddenField").value = JSON.sringify("userChoice");
When submitting the page or going to the next page the hidden value will contain the JSON string, so you can parse it as JSON again.
Server Side: (.NET)
string userChoiceHiddenField = request["userChoiceHiddenField"].ToString();
and then take the value you got and place it back in the hidden field and the JavaScript value as follows:
userChoice = JSON.parse(document.getElementById("userChoiceHiddenField").value);
Hope that answers your question.