I have this code installed on my website and what it is suppose to do is see if a product has a single or double attribute and then it disables products from there that are out of stock so customers can't click on it. The code works great for the single attribute products but it doesn't seem to work for the double attribute products.
Single Attribute Product link - https://true-grit-running-company.shoplightspeed.com/goodr-sunglasses.html
Double Attribute Product link - https://true-grit-running-company.shoplightspeed.com/womens-bondi-7.html?id=65554420&quantity=1
`
// A quick check to see if it is a product being viewed (checking the microdata) - to avoid running the rest of the code if viewing a page other than the product page
if ($('[itemtype*="//schema.org/Product"]').length > 0) {
//Check the url to see if a variant is being viewed or not
var curl = location.href;
//choose the appropriate ajax url
if (curl.indexOf('?') > -1) {
var url = curl + '&format=json';
} else {
var url = '?format=json';
}
//Start the ajax call
$.ajax({
url: url,
})
// Add the disabled attribute to the variants that aren't available
.done(function(obj) {
//create a variable with the product variants
var data = obj.product.variants;
//fun a function on each variant
$.each(data, function(index, value) {
//check if any of the variants aren't available for purchase
if (!value.stock.available) {
//CODE FOR DOUBLE ATTRIBUTE VARIANTS
//check if the variants are double attribute
if (value.title.indexOf(',') > -1) {
console.log('Double Attribute matrix!');
var attribute1 = value.title.replace(/"/g,'').split(',')[0].split(": ")[1];
//only disable the variants for which the first attribute is being viewed
if ($('select[name*="matrix"]:first()').val() == attribute1) {
var option = value.title.replace(/"/g,'').split(',')[1].split(":")[1];
$('select[name*="matrix"] option:contains(' + option + ')').each(function(){
if ($(this).text() == option) {
$(this).attr('disabled', 'true');
}
});
}
//CODE FOR SINGLE ATTRIBUTE VARIANTS
} else {
console.log('Single Attribute matrix!');
var option = value.title.split(': ')[1];
var selectname = value.title.split(': ')[0];
$('select[name*="matrix"] option:contains(' + option + ')').each(function(){
if ($(this).text() == option) {
$(this).attr('disabled', 'true');
}
});
}
}
})
});
} else {
console.log('not a product page!');
}
`
Is it possible to fill the duedate field based on the terms field? For example, I have a date in the duedate field, but I want to extend it based on the terms field. How can I do it? Through Suitescript or workflow?
The code is incomplete because I don’t know if I’m on the right path.
(NB: It is a user event)
function beforeLoad(context) {
}
function beforeSubmit(context) {
}
function afterSubmit(context) {
var dataEntrega = context.currentRecord
currentRecordRecord.getValue({
fieldId: 'duedate'
})
var dataCondicoes = context.currentRecord
currentRecord.getValue({
fieldId: 'terms'
})
}
return {
beforeLoad: beforeLoad,
beforeSubmit: beforeSubmit,
afterSubmit: afterSubmit
}
Why do you need to script this? I believe the due date is calculated based on the terms out of the box, no need for scripting
The following code should work. Depending on your needs I recommend adding some limitations to when this logic is executed. For example you can only execute based on if the transaction/record mode is create/copy (decide if you want to include edit or not). You can also check the status of the transaction, and only execute if the status is not partially paid/paid in full...
function afterSubmit(context) {
//load record
var curRec = context.newRecord; //can substitute "context.oldRecord", or "currentRecord.get();"
//get current due date
var transDueDate = curRec.getValue({fieldId: 'duedate'});
//get the terms, this will likely come as an internal id. use getText if you want the text.
var transTerms = curRec.getValue({fieldId: 'terms'});
//empty string to hold terms as a number of days
var addtlDays;
//transform the internal id to terms as a number of days
switch (transTerms){
case 1: // Ex: 1 = internal id for term "Net 15"
addtlDays = 15;
break;
case 2: // Ex: 2 = internal id for term "Net 30"
addtlDays = 30;
break;
//add additional case statements as needed
default:
addtlDays = 0;
}
//calculuate the new due date
var d = new Date(transDueDate);
var newDueDate = d.setDate(d.getDate() + addtlDays);
//set the new due date
curRec.setValue({
fieldId: 'duedate',
value: newDueDate,
ignoreFieldChange: true //optional, default is false
});
}
I’ve written a function to retrieve either locationquantityavailable or locationquantityonhand from another subsidiary.
The assumption is the user has visibility of both subsidiaries, in a scenario where a sales order is being entered under Subsidiary A, and a SO item custom field is to show the items stock on hand in Subsidiary B.
At the moment I’m getting a null result from the saved search below. I’m sure I’m missing something very basic.
The logger line is correctly giving results: Get Stock Sub:36 Loc:16 Item:4448
I know there IS both onhand and available stock in the source sub/loc/item combination.
function getStock(sub, loc, itemid, available ) {
try {
var subsidiary = isNum(sub),
location = isNum(loc),
item = isNum(itemid);
if (subsidiary == 0 || location == 0 || item == 0) {return 0}
var filters = [ new nlobjSearchFilter('subsidiary', null, 'anyof', sub ),
new nlobjSearchFilter('location', null, 'anyof', loc ),
new nlobjSearchFilter('internalid', null, 'is', itemid )];
var columns = [ new nlobjSearchColumn('subsidiary' ),
new nlobjSearchColumn('location' ),
new nlobjSearchColumn('internalid' ),
new nlobjSearchColumn('locationquantityonhand' ),
new nlobjSearchColumn('locationquantityavailable' )];
logger('Get Stock Sub:'+subsidiary+' Loc:'+location+' Item:'+item);
logger(filters);
logger(columns);
var res = nlapiSearchRecord('item',null,filters,columns);
logger('Get Stock Search:');logger(res);
if (res == null ) {
return 0;
}
if (available) {
return res[0].getValue('locationquantityavailable');
}
else {
return res[0].getValue('locationquantityonhand');
}
} catch (err) { logger(err) }
}
Disregard... doh!! inventory versus inventorylocation in the search. not enough coffee
I'm relatively new to js and now have to implement a handsontable into our project.
This worked well so far, but I am hitting a roadblock with globalization.
Basically, we use comma as a decimal seperator, but when I try and copy something like "100,2" into a cell designated as 'numeric,' it will show as 1002.
If the same value is entered in a cell designated as 'text' and the type is changed to numeric afterwards, the value will be shown correctly.
For this I already had to add 'de' culture to the table sourcecode.(basically copying 'en' and changing the values currently relevant to me.)
numeral.language('de', {
delimiters: {
thousands: '.',
decimal: ','
},//other non-relevant stuff here
When I copy the values directly from the table and insert them to np++ they show as 100.2 etc. However, when inserting them into handsontable the arguments-array looks as follows:
[Array[1], "paste", undefined, undefined, undefined, undefined]
0: Array[4]
0: 1 //row
1: 1 //column
2: "100.2" //previous value
3: 1002 //new value
Here's what I have tried currently:
hot.addHook("beforeChange", function () {
if (arguments[1] === "paste") {
hot.updateSettings({
cells: function (row, col, prop) {
var cellProperties = {
type: 'numeric',
language: 'en'
};
return cellProperties;
}
});
//hot.updateSettings({
// cells: function (row, col, prop) {
// var cellProperties = {
// type: 'text',
// };
// return cellProperties;
// }
//});
}
}, hot);
hot.addHook("afterChange", function () {
if (arguments[1] === "paste") {
ChangeMatrixSettings(); //reset cell properties of whole table
}
}, hot);
I hope I've made my problem clear enough, not sure if I missed something.
Are there any other ways to get the correct values back into the table? Is this currently not possible?
Thanks in advance.
You asked more than one thing, but let me see if I can help you.
As explained in handsontable numeric documentation, you can define a format of the cell. If you want '100,2' to be shown you would format as follows
format: '0.,'
You can change that to what you really need, like if you are looking for money value you could do something like
format: '0,0.00 $'
The other thing you asked about is not on the latest release, but you can check it out how it would work here
I have since implemented my own validation of input, due to other requirements we have for the table mainly in regards to showing invalid input to user.
function validateInputForNumeric(parameter) {
var value = parameter[3];
var row = parameter[0];
var col = parameter[1];
if (decimalSeperator === '') {
var tmpculture = getCurrCulture();
}
if (value !== null && value !== "") {
if (!value.match('([a-zA-Z])')) {
if (value.indexOf(thousandSeperator) !== -1) {
value = removeAndReplaceLast(value, thousandSeperator, ''); //Thousandseperators will be ignored
}
if (value.indexOf('.') !== -1 && decimalSeperator !== '.') {
//Since numeric variables are handled as '12.3' this will customize the variables to fit with the current culture
value = removeAndReplaceLast(value, '.', decimalSeperator);
}
//Add decimalseperator if string does not contain one
if (numDecimalPlaces > 0 && value.indexOf(decimalSeperator) === -1) {
value += decimalSeperator;
}
var index = value.indexOf(decimalSeperator)
var zerosToAdd = numDecimalPlaces - (value.length - index - 1);
for (var j = 0; j < zerosToAdd; j++) {
//Add zeros until numberOfDecimalPlaces is matched for uniformity in display values
value += '0';
}
if (index !== -1) {
if (numDecimalPlaces === 0) {
//Remove decimalseperator when there are no decimal places
value = value.substring(0, index)
} else {
//Cut values that have to many decimalplaces
value = value.substring(0, index + 1 + numDecimalPlaces);
}
}
if (ErrorsInTable.indexOf([row, col]) !== -1) {
RemoveCellFromErrorList(row, col);
}
} else {
AddCellToErrorList(row, col);
}
}
//console.log("r:" + row + " c:" + col + " v:" + value);
return value;
}
The inputParameter is an array, due to handsontable hooks using arrays for edit-events. parameter[2] is the old value, should this be needed at any point.
This code works reasonably fast even when copying 2k records from Excel (2s-4s).
One of my main hindrances regarding execution speed was me using the handsontable .getDataAtCell and .setDataAtCell methods to check. These don't seem to handle large tables very well ( not a critique, just an observation ). This was fixed by iterating through the data via .getData method.
I am wondering how can I achieve pagination using Cassandra.
Let us say that I have a blog. The blog lists max 10 posts per page. To access next posts a user must click on pagination menu to access page 2 (posts 11-20), page 3 (posts 21-30), etc.
Using SQL under MySQL, I could do the following:
SELECT * FROM posts LIMIT 20,10;
The first parameter of LIMIT is offset from the beginning of result set and second argument is amount of rows to fetch. The example above returns 10 rows starting from row 20.
How can I achieve the same effect in CQL?
I have found some solutions on Google, but all of them require to have "the last result from previous query". It works for having "next" button to paginate to another 10-results-set, but what if I want to jump from page 1 to page 5?
You don't need to use tokens, if you are using Cassandra 2.0+.
Cassandra 2.0 has auto paging.
Instead of using token function to create paging, it is now a built-in feature.
Now developers can iterate over the entire result set, without having to care that it’s size is larger than the memory. As the client code iterates over the results, some extra rows can be fetched, while old ones are dropped.
Looking at this in Java, note that SELECT statement returns all rows, and the number of rows retrieved is set to 100.
I’ve shown a simple statement here, but the same code can be written with a prepared statement, couple with a bound statement. It is possible to disable automatic paging, if it is not desired. It is also important to test various fetch size settings, since you will want to keep the memorize small enough, but not so small that too many round-trips to the database are taken. Check out this blog post to see how paging works server side.
Statement stmt = new SimpleStatement(
"SELECT * FROM raw_weather_data"
+ " WHERE wsid= '725474:99999'"
+ " AND year = 2005 AND month = 6");
stmt.setFetchSize(24);
ResultSet rs = session.execute(stmt);
Iterator<Row> iter = rs.iterator();
while (!rs.isFullyFetched()) {
rs.fetchMoreResults();
Row row = iter.next();
System.out.println(row);
}
Try using the token function in CQL:
https://docs.datastax.com/en/cql-oss/3.3/cql/cql_using/useToken.html
Another suggestion, if you are using DSE, solr supports deep paging:
https://cwiki.apache.org/confluence/display/solr/Pagination+of+Results
Manual Paging
The driver exposes a PagingState object that represents where we were in the result set when the last page was fetched:
ResultSet resultSet = session.execute("your query");
// iterate the result set...
PagingState pagingState = resultSet.getExecutionInfo().getPagingState();
This object can be serialized to a String or a byte array:
String string = pagingState.toString();
byte[] bytes = pagingState.toBytes();
This serialized form can be saved in some form of persistent storage to be reused later. When that value is retrieved later, we can deserialize it and reinject it in a statement:
PagingState pagingState = PagingState.fromString(string);
Statement st = new SimpleStatement("your query");
st.setPagingState(pagingState);
ResultSet rs = session.execute(st);
Note that the paging state can only be reused with the exact same statement (same query string, same parameters). Also, it is an opaque value that is only meant to be collected, stored an re-used. If you try to modify its contents or reuse it with a different statement, the driver will raise an error.
Src: https://docs.datastax.com/en/cql-oss/3.3/cql/cql_reference/cqlshPaging.html
If you read this doc "Use paging state token to get next result",
https://datastax.github.io/php-driver/features/result_paging/
We can use "paging state token" to paginate at application level.
So PHP logic should look like,
<?php
$limit = 10;
$offset = 20;
$cluster = Cassandra::cluster()->withContactPoints('127.0.0.1')->build();
$session = $cluster->connect("simplex");
$statement = new Cassandra\SimpleStatement("SELECT * FROM paging_entries Limit ".($limit+$offset));
$result = $session->execute($statement, new Cassandra\ExecutionOptions(array('page_size' => $offset)));
// Now $result has all rows till "$offset" which we can skip and jump to next page to fetch "$limit" rows.
while ($result->pagingStateToken()) {
$result = $session->execute($statement, new Cassandra\ExecutionOptions($options = array('page_size' => $limit,'paging_state_token' => $result->pagingStateToken())));
foreach ($result as $row) {
printf("key: '%s' value: %d\n", $row['key'], $row['value']);
}
}
?>
Although the count is available in CQL, so far I have not seen a good solution for the offset part...
So... one solution I have been contemplating was to create sets of pages using a background process.
In some table, I would create the blog page A as a set of references to page 1, 2, ... 10. Then another entry for blog page B pointing to pages 11 to 20, etc.
In other words, I would build my own index with a row key set to the page number. You may still make it somewhat flexible since you can offer the user to choose to see 10, 20 or 30 references per page. For example, when set to 30, you display sets 1, 2, and 3 as page A, sets 4, 5, 6 as page B, etc.)
And if you have a backend process to handle all of that, you can update your lists as new pages are added and old pages are deleted from the blog. The process should be really fast (like 1 min. for 1,000,000 rows if even that slow...) and then you can find the pages to display in your list pretty much instantaneously. (Obviously, if you are to have thousands of users each posting hundreds of pages... that number can grow quickly.)
Where it becomes more complicated is if you wanted to offer a complex WHERE clause. By default a blog shows you a list of all the posts from the newest to the oldest. You could also offer lists of posts with tag Cassandra. Maybe you want to inverse the order, etc. That makes it difficult unless you have some form of advanced way to create your index(es). On my end I have a C-like language which goes and peek and poke to the values in a row to (a) select them and if selected (b) to sort them. In other words, on my end I can already have WHERE clauses as complex as what you'd have in SQL. However, I do not yet break up my lists in pages. Next step I suppose...
Using cassandra-node driver for node js (koa js,marko js) : Pagination
Problem
Due to the absence of skip functionality, we need to work around. Below is the implementation of manual paging for node app in case of anyone can get an idea.
code for simple users list
navigate between next and previous page states
easy to replicate
There are two solutions which i am going to state here but only gave the code for solution 1 below,
Solution 1 : Maintain page states for next and previous records (maintain stack or whatever data structure best fit)
Solution 2 : Loop through all records with limit and save all possible page states in variable and generate pages relatively to their pageStates
Using this commented code in model, we can get all states for pages
//for the next flow
//if (result.nextPage) {
// Retrieve the following pages:
// the same row handler from above will be used
// result.nextPage();
//}
Router Functions
var userModel = require('/models/users');
public.get('/users', users);
public.post('/users', filterUsers);
var users = function* () {//get request
var data = {};
var pageState = { "next": "", "previous": "" };
try {
var userCount = yield userModel.Count();//count all users with basic count query
var currentPage = 1;
var pager = yield generatePaging(currentPage, userCount, pagingMaxLimit);
var userList = yield userModel.List(pager);
data.pageNumber = currentPage;
data.TotalPages = pager.TotalPages;
console.log('--------------what now--------------');
data.pageState_next = userList.pageStates.next;
data.pageState_previous = userList.pageStates.previous;
console.log("next ", data.pageState_next);
console.log("previous ", data.pageState_previous);
data.previousStates = null;
data.isPrevious = false;
if ((userCount / pagingMaxLimit) > 1) {
data.isNext = true;
}
data.userList = userList;
data.totalRecords = userCount;
console.log('--------------------userList--------------------', data.userList);
//pass to html template
}
catch (e) {
console.log("err ", e);
log.info("userList error : ", e);
}
this.body = this.stream('./views/userList.marko', data);
this.type = 'text/html';
};
//post filter and get list
var filterUsers = function* () {
console.log("<------------------Form Post Started----------------->");
var data = {};
var totalCount;
data.isPrevious = true;
data.isNext = true;
var form = this.request.body;
console.log("----------------formdata--------------------", form);
var currentPage = parseInt(form.hdpagenumber);//page number hidden in html
console.log("-------before current page------", currentPage);
var pageState = null;
try {
var statesArray = [];
if (form.hdallpageStates && form.hdallpageStates !== '') {
statesArray = form.hdallpageStates.split(',');
}
console.log(statesArray);
//develop stack to track paging states
if (form.hdpagestateRequest === 'next') {
console.log('--------------------------next---------------------');
currentPage = currentPage + 1;
statesArray.push(form.hdpageState_next);
pageState = form.hdpageState_next;
}
else if (form.hdpagestateRequest === 'previous') {
console.log('--------------------------pre---------------------');
currentPage = currentPage - 1;
var p_st = statesArray.length - 2;//second last index
console.log('this index of array to be removed ', p_st);
pageState = statesArray[p_st];
statesArray.splice(p_st, 1);
//pageState = statesArray.pop();
}
else if (form.hdispaging === 'false') {
currentPage = 1;
pageState = null;
statesArray = [];
}
data.previousStates = statesArray;
console.log("paging true");
totalCount = yield userModel.Count();
var pager = yield generatePaging(form.hdpagenumber, totalCount, pagingMaxLimit);
data.pageNumber = currentPage;
data.TotalPages = pager.TotalPages;
//filter function - not yet constructed
var searchUsers = yield userModel.searchList(pager, pageState);
data.usersList = searchUsers;
if (searchUsers.pageStates) {
data.pageStates = searchUsers.pageStates;
data.next = searchUsers.nextPage;
data.pageState_next = searchUsers.pageStates.next;
data.pageState_previous = searchUsers.pageStates.previous;
//show previous and next buttons accordingly
if (currentPage == 1 && pager.TotalPages > 1) {
data.isPrevious = false;
data.isNext = true;
}
else if (currentPage == 1 && pager.TotalPages <= 1) {
data.isPrevious = false;
data.isNext = false;
}
else if (currentPage >= pager.TotalPages) {
data.isPrevious = true;
data.isNext = false;
}
else {
data.isPrevious = true;
data.isNext = true;
}
}
else {
data.isPrevious = false;
data.isNext = false;
}
console.log("response ", searchUsers);
data.totalRecords = totalCount;
//pass to html template
}
catch (e) {
console.log("err ", e);
log.info("user list error : ", e);
}
console.log("<------------------Form Post Ended----------------->");
this.body = this.stream('./views/userList.marko', data);
this.type = 'text/html';
};
//Paging function
var generatePaging = function* (currentpage, count, pageSizeTemp) {
var paging = new Object();
var pagesize = pageSizeTemp;
var totalPages = 0;
var pageNo = currentpage == null ? null : currentpage;
var skip = pageNo == null ? 0 : parseInt(pageNo - 1) * pagesize;
var pageNumber = pageNo != null ? pageNo : 1;
totalPages = pagesize == null ? 0 : Math.ceil(count / pagesize);
paging.skip = skip;
paging.limit = pagesize;
paging.pageNumber = pageNumber;
paging.TotalPages = totalPages;
return paging;
};
Model Functions
var clientdb = require('../utils/cassandradb')();
var Users = function (options) {
//this.init();
_.assign(this, options);
};
Users.List = function* (limit) {//first time
var myresult; var res = [];
res.pageStates = { "next": "", "previous": "" };
const options = { prepare: true, fetchSize: limit };
console.log('----------did i appeared first?-----------');
yield new Promise(function (resolve, reject) {
clientdb.eachRow('SELECT * FROM users_lookup_history', [], options, function (n, row) {
console.log('----paging----rows');
res.push(row);
}, function (err, result) {
if (err) {
console.log("error ", err);
}
else {
res.pageStates.next = result.pageState;
res.nextPage = result.nextPage;//next page function
}
resolve(result);
});
}).catch(function (e) {
console.log("error ", e);
}); //promise ends
console.log('page state ', res.pageStates);
return res;
};
Users.searchList = function* (pager, pageState) {//paging filtering
console.log("|------------Query Started-------------|");
console.log("pageState if any ", pageState);
var res = [], myresult;
res.pageStates = { "next": "" };
var query = "SELECT * FROM users_lookup_history ";
var params = [];
console.log('current pageState ', pageState);
const options = { pageState: pageState, prepare: true, fetchSize: pager.limit };
console.log('----------------did i appeared first?------------------');
yield new Promise(function (resolve, reject) {
clientdb.eachRow(query, [], options, function (n, row) {
console.log('----Users paging----rows');
res.push(row);
}, function (err, result) {
if (err) {
console.log("error ", err);
}
else {
res.pageStates.next = result.pageState;
res.nextPage = result.nextPage;
}
//for the next flow
//if (result.nextPage) {
// Retrieve the following pages:
// the same row handler from above will be used
// result.nextPage();
//}
resolve(result);
});
}).catch(function (e) {
console.log("error ", e);
info.log('something');
}); //promise ends
console.log('page state ', pageState);
console.log("|------------Query Ended-------------|");
return res;
};
Html side
<div class="box-footer clearfix">
<ul class="pagination pagination-sm no-margin pull-left">
<if test="data.isPrevious == true">
<li><a class='submitform_previous' href="">Previous</a></li>
</if>
<if test="data.isNext == true">
<li><a class="submitform_next" href="">Next</a></li>
</if>
</ul>
<ul class="pagination pagination-sm no-margin pull-right">
<li>Total Records : $data.totalRecords</li>
<li> | Total Pages : $data.TotalPages</li>
<li> | Current Page : $data.pageNumber</li>
</ul>
</div>
I am not very much experienced with node js and cassandra db, this solution can surely be improved. Solution 1 is working example code to start with the paging idea. Cheers
a detailed blog.
Our use case was similar. Pull everything from a Cassandra table (cassandra does it smartly by fetching ~5000 in one go and return a cursor), heavy personalized processing on each row, and keep going. Once our iteration reaches close to 5000, it again fetches the next chunk of 5000 rows internally and adds it to the result cursor. It does it so brilliantly that we don’t even feel this magic happening behind the scene.
but It became a bottleneck for us.As iterating over the chunk took some time and till it reached the end of the chunk, Cassandra thought the connection was not being used and closed the connection automatically yelling, its timeout. So we implemented with page state.
from cassandra.cluster import Cluster
from cassandra.auth import PlainTextAuthProvider
from cassandra.query import SimpleStatement
# connection with cassandra
cluster = Cluster(["127.0.0.1"], auth_provider=PlainTextAuthProvider(username="pankaj", password="pankaj"))
session = cluster.connect()
# setting keyspace
session.set_keyspace("my_keyspace")
# set fetch size
fetch_size = 100
# It will print first 100 records
next_page_available = True
paging_state = None
data_count = 0
while next_page_available is True:
# fetches a new chunk with given page state
result = fetch_a_fresh_chunk(paging_state)
paging_state = results.paging_state
for result in results:
# process payload here.....
# payload processed
data_count += 1
# once we reach fetch size, we stop cassandra to fetch more chunk, internally
if data_count == fetch_size:
i = 0
break
# fetches a fresh chunk with given page state
def fetch_a_fresh_chunk(paging_state = None)
query = "SELECT * FROM my_cute_cassandra_table;"
statement = SimpleStatement(query, fetch_size = fetch_size)
results = session.execute(statement, paging_state=paging_state)