Calling JS function after BIND? - javascript

I'm building a ColdFusion 10 form, similar to an invoice. It contains three columns.
The first column is a filter to select a subset of all available products: it contains cfselect that are built from a query of product categories.
The second column is the product itself: it contains cfselect that are BINDed to a table containing a product list, only showing the products from the matching category.
The third column is the product price: it contains cfdiv that are BINDed to the product prices, in the same table.
I'm looking for a way to compute the total price (sum) of the selected items. I'm especially looking for the "something has changed" trigger to attach to.
Solutions that I see:
the total is another BIND, depending on the price on each item. A bit ugly, since this would be done server-side, even if a simple JS could do it.
use onChange on the cfselect. In that case, how can I ensure that the price BIND have been performed before the total is computed?
use onChange on the cfselect, and directly query the prices in that script (i.e. remove the BIND on the price columns) with XMLHttpRequest. I wrote some code to do this and it seems to work, but I still need to change it to asynchronous requests, and decode the return in an browser-independent way. This seems way more complex than needed.
use a (non-existing) onChange on the cfdiv. This would be the easiest... but that hook doesn't exist.
use a cfselect/cfinput instead of the cfdiv in the third column, and cheat with css to make it look like a div. Ugly.
same as previous, but hidden, and keep the existing cfdiv visible. Probably the best bet right now, but all queries will be performed twice. I'm sure there's a better way.
use a timer. I'd rather avoid that.
What am I missing?
Thanks.

Answering my own question, in case it can helps someone else.
What I had trouble finding and that this example may illustrate
rough jQuery equivalent of simple CF BIND
example of $.get() that actually do someting with the downloaded data, not just alert() it
$.get() definitely isn't the recommended way to get introduced to jQuery.
This will probably make the "pros" shout in horror. It shouldn't be considered copy-paste code, only hints on figuring a way to do it.
It actually has been strongly edited from my working version, so it may contain dummy typos.
OK, enough disclaimers.
function dollarize (price) {
// unrelated code - just ensure that the price is always displayed with two decimals
}
function downloadPrice(url, DOM_Item_ID) {
$.get( url,
function(data,status){ // keep in mind that this function is called ASYNCHRONOUSLY
//alert(data); // typical data received, for a $1 item: <wddxPacket version='1.0'><header/><data><string>1</string></data></wddxPacket>
var payload = $(data).find('string').text();
$('##' + DOM_Item_ID).text('$' + dollarize(parseFloat(payload))); <!--- normally a single # - doubled since in a <cfoutput> --->
UpdateTotalPrice();
},
"xml");
}
function UpdateTotalPrice() {
var price = 0;
for(var e=1;e<=#MAX_NUMBER_ITEM#;e++)
{
var node = document.getElementById("Item_"+e);
var ID = node.selectedIndex;
if(ID != 0) {
prix += parseFloat(document.getElementById("Price_"+e).childNodes[0].nodeValue.substring(1)); // substring(1): removes the $ added above
}
}
document.getElementById('TotalPrice').childNodes[0].nodeValue = '$' + dollarize(prix);
}
function onChangeItem(e) {
var ID = document.getElementById("Item_"+e).value;
downloadPrice("#application.CFC_PATH#gestion-equipements.cfc?method=trouvePrixStandardEquipement&ID="+ID,
"Price_"+e);
}
2nd column:
<cfloop from="1" to="#MAX_NUMBER_ITEM#" index="e">
...
<cfselect Name="Item_#e#" ID="Item_#e#" bind="........." bindonload="yes" queryPosition="below" onChange="onChangeItem(#e#)"><option value="0">--</cfselect>
Each price item in the 3rd column:
<cfdiv ID="Price_#e#" align="right">$0.00</cfdiv> <!--- content of the div (i.e. $0.00) must NOT be empty, otherwise childNodes[0] above will fail --->
Total price:
<cfdiv ID="TotalPrice">$0.00</cfdiv>

Related

Tabulator.js table elements retrieve the index of the row and serve as a control element to other plots

I am using tabulator package 4.3.0 to work on a webpage. The table generated by the package is going to be the control element of a few other plots. In order to achieve this, I have been adding a dataFiltered function when defining the table variable. But instead of getting the order of the rows in my data object, I want to figure a way to get the index of the rows in the filtered table.
Currently, I searched the manual a little bit and have written the code analogue to this:
dataFiltered: function(filters,rows){
console.log(rows[0]._row.data)
console.log(rows[0].getPosition(true));
}
But the getPosition always returned -1, which refers to that the row is not found in the filtered table. I also generated a demo to show the real situ when running the function. with this link: https://jsfiddle.net/Binny92/3kbn8zet/53/.
I would really appreciate it if someone could help me explain a little bit of how could I get the real index of the row in the filtered data so that I could update the plot accordingly and why I am always getting -1 when running the code written in this way.
In addition, I wonder whether there is a way to retrieve the data also when the user is sorting the table. It's a pity that code using the following strategy is not working in the way I am expecting since it is not reacting to the sort action and will not show the information when loading the page for the first time.
$('#trialTable').on('change',function(x){console.log("Yes")})
Thank you for your help in advance.
The reason this is happening is because the dataFiltered callback is triggered after the rows are filtered but before they have been laid out on the table, so they wont necessarily be ready by the time you call the getPosition function on them.
You might do better to use the renderComplete callback, which will also handle the scenario when the table is sorted, which would change the row positions.
You could then use the getRows function passing in the value "active" as the first augment return only rows that have passed the filter:
renderComplete: function(){
var rows = table.getRows("active");
console.log(rows[0].getPosition(true));
}
On a side note i notice you are trying to access the _row property to access the row data. By convention underscore properties are considered private in JavaScript and should not be accessed as it can result in unstable system behaviour.
Tabulator has an extensive set of functions on the Row Component to allow you to access anything you need. In the case of accessing a rows data, there is the getData function
var data = row.getData();

How to get a dataFiltered-Callback for a specific Column Header Filters?

as far as I understand there is only the callback dataFiltered, which is used for the whole table. It is triggered by all filters indifferently.
Is it possible to get a callback for a specific single header filter?
So that I can call a function as soon as a certain header filter becomes active?
I imagine it to be something like this:
{title:"Name", field:"name", headerFilter:true, headerdataFiltered:function()}
Is there possibly a workaround?
Thanks a lot!
(I would be especially grateful for non-jquery solutions)
Thanks also for this realy wonderful tool Tabulator.
Thanks for your kind words, it is always great to hear that Tabulator is appreciated.
The reason it is called when any filter is applied is because multiple filters can be applied at once, with Complex Filtering a complex and/or filter set can be applied so it would be hard to isolate down to specific columns in all cases.
The dataFiltered callback does get passed a list of all currently active filters so you can see if your affected column is in that:
var table = new Tabulator("#example-table", {
dataFiltering:function(filters){
//filters - array of filters currently applied
},
});
If you need to see if the column has just been filtered you can store a copy of the previous value of this object outside the callback and then compare the old and new values on the next call.
The other option would be to use a custom editor in the header filter then you could manually decide when the success function is called that initiates the filter and then reference an external function from there

Check Shopify variant stock from variant ID in JavaScript with JSON

I've created a JQuery script that has multiple selector forms to narrow down a complicated multi-sku product in Shopify.
By the time the customer has selected everything, the shopify variant IDs are returned as variables via an array (not getting data directly from Shopify).
e.g.
var idone = "15065378226219"
var idtwo = "13367973249067"
Now I want to use these values to get the variant inventory quantity. I've tried this code
function getValues(callback) {
$.getJSON("/admin/variants/15065378226219.json",function(result){
callback(result); // this should be the return value
});
}
getValues(function(values) {
alert(values);
});
But the alert doesn't do anything, so I don't think anything's happening. My goal is to get the variant inventories for both IDs so I can validate in JS whether or not the product is available (both IDs have inventory greater than 0).
Any guidance would be appreciated!
Thanks
When you access your complex product in Liquid, there is a filter that shows the product off as JSON. Amazingly, this filter is 'json'. So try this Liquid:
var fizzborked = {{ product | json }};
You can inspect that in a comment, or however you wish. Look for your variants. Note the inventory quantity being there. Use that to build your logic. You can no longer make callbacks to Shopify for inventory amounts. 10 years ago people were complaining about having their inventory levels scraped, so Shopify cut that off.
As far as more sophisticated approaches go, you could always use a private App to support a Proxy call, allowing you to make secure Ajax calls to cull whatever data you need to support your use-case. Win Win!

Datatables: can you use each() inside an event listener?

I have a Datatable where I need to do two things:
Sum all the figures in a column.
Find out if the cells in another column have a certain value and, if so, blank them. (Reason: a MySQL table that has a default date of "1970-01-01" if you don't insert the date. I want to replace all the "1970-01-01" instances with an empty cell).
To do that, I attached an event listener to the draw event, and then:
For 1) I used the sum() Datatables plugin.
For 2), I followed this answer: Find and replace cells value with Datatables plugin, and used each().
var tabla=$('#list').DataTable();
tabla.on('draw.dt', function() {
var bla=tabla.column(10).data().sum();
tabla.column(9).nodes.each(function(node, index, dt) {
var ble=tabla.cell(node).data();
if (/^1970-01-01/.test(ble)) {
table.cell(node).data('');
}
});
});
The problem is that, when I execute this, I get a "tabla.column(...).nodes.each is not a function". Searching around, I find that this error is caused most of the times when jQuery or the datatable haven't been initialized yet... but in my case, they have most certainly been, because the first thing (the sum()) does get done; in other words, the "bla" variable does get calculated.
This makes me wonder whether you can use each() inside an event trigger; otherwise, what could be causing this?
You could use the $.each function to iterate over an arbitrary collection
However, and though I'm not sure what's your intention regarding the sum of the column, I'd recommend working with the render option for each column involved.
For instance, in the case of the date column you could have a render function like this:
render: function (data,type,fullRow){
if(data === '1970-01-01') {
return null;
}
else {
return data;
}
}
(Disclaimer: not tested!)
The drawCallback function (and the draw event, for that matter ) gets called every single time Datatables need to redraw the table: initial loading, filtering, sorting, new row added, etc. This might have some unforeseen collateral effects, or simply an impact in performance.
Hope it helps you!

Using protractor to test infinite scrolling

I don't think this is strictly infinite scrolling but it was the best i could think of that compares to what i am seeing.
Anyway, we are using ng-grid to show data in a table. We have roughly 170 items (rows) to display. when we use ng-grid it creates a repeater. When i inspect this repeater from the browser its limited to 35 items and, as you scroll down the list, you start to lose the top rows from the dom and new rows are added at the bottom etc (hence why i don't think its strictly infinite scrolling as that usually just adds more rows)
Just so I'm clear there is always 35 'ng-repeat=row in rendered rows' elements in the dom no matter how far you have scrolled down.
This is great until it comes to testing. I need to get the text for every item in the list, but using element.all(by.binding('item.name')) or by.repeater or by.css doesn't help as there is only ever 35 items present on the page.
Now to my question, how can i make it so that i can grab all 170 items as an object that i can then iterate through to grab the text of and store as an array?
on other pages where we have less than 35 items iv just used the binding to create an object, then using async.js to go over each row and get text (see below for an example, it is modified extract i know it probably wouldn't work as it is, its just to give you reference)
//column data contains only 35 rows, i need all 170.
var columnData = element.all(by.binding('row.entity.name'))
, colDataTextArr = []
//prevOrderArray gets created elsewhere
, prevOrderArray = ['item1', 'item2'... 'item 169', 'item 170'];
function(columnData, colDataTextArr, prevOrderArray){
columnData.then(function(colData){
//using async to go over each row
async.eachSeries(colData, function(colDataRow, nRow){
//get the text for the current async row
colDataRow.getText().then(function(colDataText){
//add each rows text to an array
colDataTextArr.push(colDataText);
nRow()
});
}, function(err){
if(err){
console.log('Failed to process a row')
}else{
//perform the expect
return expect(colDataTextArr).toEqual(prevOrderArray);
}
});
});
}
As an aside, I am aware that iterating through 170 rows and storing the text in an array isn't very efficient so if there is a better way of doing that as well I'm open to suggestions.
I am fairly new to JavaScript and web testing so if im not making sense because I'm using wrong terminology or whatever let me know and i'll try and explain more clearly.
I think it is an overkill to test all the rows in the grid. I guess it would be sufficient to test that you get values for the first few rows and then, if you absolutely need to test all the elements, use an evaluate().
http://angular.github.io/protractor/#/api?view=ElementFinder.prototype.evaluate
Unfortunately there is no code snippet in the api page, but it would look something like this:
// Grab an element where you want to evaluate an angular expression
element(by.css('grid-selector')).evaluate('rows').then(function(rows){
// This will give you the value of the rows scope variable bound to the element.
expect(rows[169].name).toEqual('some name');
});
Let me know if it works.

Categories