I have this code which displays text from a Web SQL database:
<span contenteditable="true"
onkeyup="updateRecord('+item['id']+', this)">' + item['product'] + '</span>
When I edit the text it calls the updateRecord function and updates the value.
function updateRecord(id, textEl) {
db.transaction(function(tx) {
tx.executeSql("UPDATE products SET product = ? WHERE id = ?",
[textEl.innerHTML, id], null, onError);
});
}
I have several of these values I'm trying to work with though. So I would like to specify the column. The above code works if I set the column to product in the function, in the following code I'm trying to send an additional parameter to the function but it's not working. What am I doing wrong here?
<span contenteditable="true"
onkeyup="updateRecord('+item['id']+', 'product', this)">'+ item['product'] + '</span>
function updateRecord(id, column, textEl) {
db.transaction(function(tx) {
tx.executeSql("UPDATE products SET ? = ? WHERE id = ?",
[column, textEl.innerHTML, id], null, onError);
});
}
Same with any full-bodied DBMS, you cannot parameterize the column names, only values.
So you need to do it like this
<span contenteditable="true"
onkeyup="updateRecord('+item['id']+', 'product', this)">'+ item['product'] + '</span>
function updateRecord(id, column, textEl) {
db.transaction(function(tx) {
tx.executeSql("UPDATE products SET " + column + " = ? WHERE id = ?",
[column, textEl.innerHTML, id], null, onError);
});
}
Related
I followed the recommendations I found here
jQuery(document).ready(function($) {
jQuery.fn.dataTable.Api.register( 'sum()', function ( ) {
return this.flatten().reduce( function ( a, b ) {
if ( typeof a === 'string' ) {
a = a.replace(/[^\d.-]/g, '') * 1;
}
if ( typeof b === 'string' ) {
b = b.replace(/[^\d.-]/g, '') * 1;
}
return a + b;
}, 0 );
} );
$("#crudTable tfoot").css("display", "table-footer-group");
crud.table.on("draw.dt", function ( row, data, start, end, display ) {
total = crud.table.rows( function ( idx, data, node ) {
return data[11].includes('Cancelado') ? false : true;} ).data().pluck(10).sum();
total = "$" + total.toLocaleString(undefined, {minimumFractionDigits: 2, maximumFractionDigits: 2});
$("#crudTable tfoot tr th").html(
" <br> "
);
$("#crudTable tfoot tr").children().eq(10).html(
"Total <br>"+ total
);
});
});
And I added some modifications to get the total of the column by skipping the items that have canceled status, but I have not been able to get the total of the records but without paging. With Datatable I get the records that are being drawn, but I can't find how to intercept the ajax query or modify it to get the full total on that column including filter modifications.
Currently if in the pagination I request "show all records" obviously I get the value I need. But the requirement is that this value is displayed even if the table is visually paginated.
one way to achieve that would be to overwrite the search() function of the ListOperation (it's the table ajax endpoint).
You would need do do the full query without the pagination part to get the full data, and then pass the calculation along with the paginated response for display.
Cheers
When building a complex SQL query for Laravel, using ? as placeholders for parameters is great. However when the value is null, the SQL syntax needs to be changed from = ? to IS NULL. Plus, since the number of parameters is one less, I need to pass a different array.
To get it to work, I have written it like this, but there must be a better way:
if ($cohortId === null) {
// sql should be: column IS NULL
$sqlCohortString = "IS NULL";
$params = [
Carbon::today()->subDays(90),
// no cohort id here
];
} else {
// sql should be: column = ?
$sqlCohortString = "= ?";
$params = [
Carbon::today()->subDays(90),
$cohortId
];
}
$query = "SELECT items.`name`,
snapshots.`value`,
snapshots.`taken_at`,
FROM snapshots
INNER JOIN (
SELECT MAX(id) AS id, item_id
FROM snapshots
WHERE `taken_at` > ?
AND snapshots.`cohort_id` $sqlCohortString
GROUP BY item_id
) latest
ON latest.`id` = snapshots.`id`
INNER JOIN items
ON items.`id` = snapshots.`item_id`
ORDER by media_items.`slug` ASC
";
$chartData = DB::select($query, $params);
My question is: does Laravel have a way to detect null values and replace ? more intelligently?
PS: The SQL is for a chart, so I need the single highest snapshot value for each item.
You can use ->when to create a conditional where clause:
$data = DB::table('table')
->when($cohortId === null, function ($query) {
return $query->whereNull('cohort_id');
}, function ($query) use ($cohortId) {
// the "use" keyword provides access to "outer" variables
return $query->where('cohort_id', '=', $cohortId);
})
->where('taken_at', '>', $someDate)
->toSql();
I am trying to search a two column table with a CustomerID and a ServID. The primary key is the combination of both. I want the query to search if the combination is there. If it is return something I can use in c# to cancel the input or if it is not found have it insert the record. This is as far as I have made it after hours of searching.
IF (SELECT * FROM Cus_Ser WHERE customerID=inputnumber AND ServID=inputnumber) IS NULL
PRINT 'Already Exists'
ELSE
INSERT INTO Cus_Ser(CustomerID,ServID) VALUES(inputnumber, inputnumber)
Here is the code
#using WebMatrix.Data
#{
var CustomerID = Request.QueryString["CustomerID"];
var ServID = 0;
var db = Database.Open("Azure");
var selectedData = db.Query("SELECT * FROM Customer INNER JOIN Cus_Ser ON Customer.customerID=Cus_Ser.customerID INNER JOIN Service ON Cus_Ser.ServID=Service.ServID WHERE customer.CustomerID =" + CustomerID);
var grid = new WebGrid(source: selectedData);
var selectedData2 = db.Query("SELECT * FROM Service");
var grid2 = new WebGrid(source: selectedData2);
if (IsPost)
{ if (int.TryParse(Request.Form["ServID"], out int numbertest))
{
ServID = Int32.Parse(Request.Form["ServID"]);
var dbCommandSearch = "SELECT * FROM Cus_Ser WHERE customerID=" + CustomerID + " AND ServID=" + ServID;
//search = db.QuerySingle(dbCommandSearch);
//if ((CustomerID, "ServID") != search
db = Database.Open("Azure");
var insertCommand = "INSERT INTO cus_ser (customerID,ServID)VALUES(" + CustomerID + ", " + ServID + ")";
db.Execute(insertCommand);
Response.Redirect("~/ViewService?CustomerID=" + CustomerID);
}
//else
//{
//Validation.AddFormError("Entered Value is Not a Service Numberr");
//}
else
{
Validation.AddFormError("Entered Value is NOT a Service Number");
}
} }
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Add Service</title>
<style>
.validation-summary-errors {
border: 2px dashed red;
color: red;
font-weight: bold;
margin: 12px;
}
</style>
</head>
<body>
<form method="post">
<fieldset>
<legend>Input Service To Add</legend>
<p>
<label for="ServID">Service ID:</label>
<input type="text" name="ServID" value="#ServID" />
</p>
<input type="hidden" name="CustomerID" value="#CustomerID" />
<p><input type="submit" name="buttonSubmit" value="Submit Changes" /></p>
</fieldset>
</form>
<p>Return to customer listing</p>
Multiple Inline SQL Statements
Your sample code seems to be very close. The QuerySingle method will return null if no matches are found. Thus you can check for a null value before issuing the insert statement, with something like:
ServID = Int32.Parse(Request.Form["ServID"]);
var dbCommandSearch = "SELECT * FROM Cus_Ser WHERE customerID=" + CustomerID + " AND ServID=" + ServID;
search = db.QuerySingle(dbCommandSearch);
if (search == null) { // record does not already exist
var insertCommand = "INSERT INTO cus_ser (customerID,ServID)VALUES(" + CustomerID + ", " + ServID + ")";
db.Execute(insertCommand);
Response.Redirect("~/ViewService?CustomerID=" + CustomerID);
}
else
{
Validation.AddFormError("Entered Value is Not a Service Numberr");
}
Single SQL Statement
You can combine this all into one big SQL statement, something like:
var sql = #"IF EXISTS (SELECT 1 FROM Cus_Ser WHERE customerID=#0 AND ServID=#1)
SELECT 0 as [Inserted];
ELSE
BEGIN
INSERT INTO Cus_Ser(CustomerID,ServID) VALUES(#0, #1)
SELECT 1 as [Inserted];
END";
You can execute this as a parameterized query, which is much safer* than the code you have:
var response = db.QuerySingle(sql, CustomerID, ServID); // passing the input value as a parameter
if (response.Inserted == 1) {
// WebMatrix might convert to a bool, in which you would want `response.Inserted == true` instead
Response.Redirect("~/ViewService?CustomerID=" + CustomerID);
}
else
{
Validation.AddFormError("Entered Value is Not a Service Numberr");
}
As I am not deeply familiar with WebMatrix, I am not 100% sure about this code. Hopefully it sets you in the right direction.
A word of warning: your original code is vulnerable to something called a SQL injection attack. Most programmers start out making that mistake; I know that I did. In summary: that inputnumber parameter could have a SQL statement in it instead of a number. If someone crafts that SQL Statement just right, then they could cause a lot of damage. For an entry-level programming assignment it is not something to worry about in detail. But as you progress in coding, you will need to learn how to use parameterized queries in order to guard against SQL injection. A few useful references:
SQL Injection
Introduction to Working with a Database in ASP.NET Web Pages (Razor) Sites
I used to name my parameters in my SQL query when preparing it for practical reasons like in php with PDO.
So can I use named parameters with node-postgres module?
For now, I saw many examples and docs on internet showing queries like so:
client.query("SELECT * FROM foo WHERE id = $1 AND color = $2", [22, 'blue']);
But is this also correct?
client.query("SELECT * FROM foo WHERE id = :id AND color = :color", {id: 22, color: 'blue'});
or this
client.query("SELECT * FROM foo WHERE id = ? AND color = ?", [22, 'blue']);
I'm asking this because of the numbered parameter $n that doesn't help me in the case of queries built dynamically.
There is a library for what you are trying to do. Here's how:
var sql = require('yesql').pg
client.query(sql("SELECT * FROM foo WHERE id = :id AND color = :color")({id: 22, color: 'blue'}));
QueryConvert to the rescue. It will take a parameterized sql string and an object and converts it to pg conforming query config.
type QueryReducerArray = [string, any[], number];
export function queryConvert(parameterizedSql: string, params: Dict<any>) {
const [text, values] = Object.entries(params).reduce(
([sql, array, index], [key, value]) => [sql.replace(`:${key}`, `$${index}`), [...array, value], index + 1] as QueryReducerArray,
[parameterizedSql, [], 1] as QueryReducerArray
);
return { text, values };
}
Usage would be as follows:
client.query(queryConvert("SELECT * FROM foo WHERE id = :id AND color = :color", {id: 22, color: 'blue'}));
Not exactly what the OP is asking for. But you could also use:
import SQL from 'sql-template-strings';
client.query(SQL`SELECT * FROM unicorn WHERE color = ${colorName}`)
It uses tag functions in combination with template literals to embed the values
I have been working with nodejs and postgres. I usually execute queries like this:
client.query("DELETE FROM vehiculo WHERE vehiculo_id= $1", [id], function (err, result){ //Delete a record in de db
if(err){
client.end();//Close de data base conection
//Error code here
}
else{
client.end();
//Some code here
}
});
I'm developing a prestashop module that has to make lists of existing products.
For the configuration panel of the module, using renderForm() and getContent(), I'm trying to replicate the "accesories" capability, where you start writing some info of a product on an input, and it shows the products that are a match. When selecting that product, it gets added on a list. Like this:
This a screenshot of Catalog / Products / Associations tab.
I'm trying with PS 1.6.0.14 and PS1.6.1.0RC3. How would I replicate this functionality to get lists of products on a module configuration panel?
I tried looking here Prestashop AdminProductsController.php but I don't really understand where half of that info is coming from.
There is an autocomplete plugin in prestashop you got to use that for this. Its in js->jquery->plugins you got to add this plugin into your module to make it work.
I think that to achieve that functionality, the renderForm() function won't be enough since you have to bind some javascript and some custom html.
The process of writing a fully functional module is a bit long but by taking the accessories functionality as a starting point it wont be so hard and you will always have a reference on "how-to-do-it".
I would go with this:
1) first create your
getContent()
function to be able to show the custom template and the product associated by your module so we will have something along:
public function getContent(){
//post process part to save the associations
if(Tools::isSubmit('saveMyAssociations'){
... //we will see it later
}
$my_associations = MyModule::getAssociationsLight($this->context->language->id,Tools::getValue('id_product')); //function that will retrieve the array of all the product associated on my module table.
$this->context->smarty->assign(array(
'my_associations' => $my_associations,
'product_id' => (int)Tools::getValue('id_product')
));
return $this->display(__FILE__, 'views/templates/admin/admintemplate.tpl'); //custome template to create the autocomplete
}
//our little function to get the already saved list, for each product we will retrieve id, name and reference with a join on the product/product_lang tables.
public static function getAssociationsLight($id_lang, $id_product, Context $context = null)
{
if (!$context)
$context = Context::getContext();
$sql = 'SELECT p.`id_product`, p.`reference`, pl.`name`
FROM `'._DB_PREFIX_.'my_associations`
LEFT JOIN `'._DB_PREFIX_.'product` p ON (p.`id_product`= `id_product_2`)
'.Shop::addSqlAssociation('product', 'p').'
LEFT JOIN `'._DB_PREFIX_.'product_lang` pl ON (
p.`id_product` = pl.`id_product`
AND pl.`id_lang` = '.(int)$id_lang.Shop::addSqlRestrictionOnLang('pl').'
)
WHERE `id_product_1` = '.(int)$id_product;
return Db::getInstance()->executeS($sql);
}
2) create a template that will be able to show the automplete and the list.
Here we will loop trough the saved associations to create our autocomplete list, and we will do it with some hidden field to keep track of the ids/name and also a visible list were we will have a delete button for each row.
<input type="hidden" name="inputMyAssociations" id="inputMyAssociations" value="{foreach from=$my_associations item=accessory}{$accessory.id_product}-{/foreach}" />
<input type="hidden" name="nameMyAssociations" id="nameMyAssociations" value="{foreach from=$my_associations item=accessory}{$accessory.name|escape:'html':'UTF-8'}¤{/foreach}" />
<div id="ajax_choose_product_association">
<div class="input-group">
<input type="text" id="product_autocomplete_input_association" name="product_autocomplete_input_association" />
<span class="input-group-addon"><i class="icon-search"></i></span>
</div>
</div>
<div id="divMyAssociations">
{foreach from=$my_associations item=accessory}
<div class="form-control-static">
<button type="button" class="btn btn-default delAssociation" name="{$accessory.id_product}">
<i class="icon-remove text-danger"></i>
</button>
{$accessory.name|escape:'html':'UTF-8'}{if !empty($accessory.reference)}{$accessory.reference}{/if}
</div>
{/foreach}
</div>
<input type="submit" name="submitMyAssociations" id="submitMyAssociations" value="Send"/>
<input type="hidden" name="productId" id="productId" value="{$product_id|escape:'html'}"/>
3) Now we can add the javascript to bind an autocomplete on the main input and perform all the logic for each action
$(document).ready(function(){
//our function wrapper.
var initMyAssociationsAutocomplete = function (){
//initialize the autocomplete that will point to the default ajax_products_list page (it returns the products by id+name)
$('#product_autocomplete_input_association')
.autocomplete('ajax_products_list.php', {
minChars: 1,
autoFill: true,
max:20,
matchContains: true,
mustMatch:true,
scroll:false,
cacheLength:0,
formatItem: function(item) {
return item[1]+' - '+item[0];
}
}).result(addAssociation);
//as an option we will add a function to exclude a product if it's already in the list
$('#product_autocomplete_input_association').setOptions({
extraParams: {
excludeIds : getAssociationsIds()
}
});
};
//function to exclude a product if it exists in the list
var getAssociationsIds = function()
{
if ($('#inputMyAssociations').val() === undefined)
return '';
return $('#inputMyAssociations').val().replace(/\-/g,',');
}
//function to add a new association, adds it in the hidden input and also as a visible div, with a button to delete the association any time.
var addAssociation = function(event, data, formatted)
{
if (data == null)
return false;
var productId = data[1];
var productName = data[0];
var $divAccessories = $('#divCrossSellers');
var $inputAccessories = $('#inputMyAssociations');
var $nameAccessories = $('#nameMyAssociations');
/* delete product from select + add product line to the div, input_name, input_ids elements */
$divAccessories.html($divAccessories.html() + '<div class="form-control-static"><button type="button" class="delAssociation btn btn-default" name="' + productId + '"><i class="icon-remove text-danger"></i></button> '+ productName +'</div>');
$nameAccessories.val($nameAccessories.val() + productName + '¤');
$inputAccessories.val($inputAccessories.val() + productId + '-');
$('#product_autocomplete_input_association').val('');
$('#product_autocomplete_input_association').setOptions({
extraParams: {excludeIds : getAssociationsIds()}
});
};
//the function to delete an associations, delete it from both the hidden inputs and the visible div list.
var delAssociations = function(id)
{
var div = getE('divMyAssociations');
var input = getE('inputMyAssociations');
var name = getE('nameMyAssociations');
// Cut hidden fields in array
var inputCut = input.value.split('-');
var nameCut = name.value.split('¤');
if (inputCut.length != nameCut.length)
return alert('Bad size');
// Reset all hidden fields
input.value = '';
name.value = '';
div.innerHTML = '';
for (i in inputCut)
{
// If empty, error, next
if (!inputCut[i] || !nameCut[i])
continue ;
// Add to hidden fields no selected products OR add to select field selected product
if (inputCut[i] != id)
{
input.value += inputCut[i] + '-';
name.value += nameCut[i] + '¤';
div.innerHTML += '<div class="form-control-static"><button type="button" class="delAssociation btn btn-default" name="' + inputCut[i] +'"><i class="icon-remove text-danger"></i></button> ' + nameCut[i] + '</div>';
}
else
$('#selectAssociation').append('<option selected="selected" value="' + inputCut[i] + '-' + nameCut[i] + '">' + inputCut[i] + ' - ' + nameCut[i] + '</option>');
}
$('#product_autocomplete_input_association').setOptions({
extraParams: {excludeIds : getAssociationsIds()}
});
};
//finally initialize the function we have written above and create all the binds.
initMyAssociationsAutocomplete();
//live delegation of the deletion button to our delete function, this will allow us to delete also any element added after the dom creation with the ajax autocomplete.
$('#divMyAssociations').delegate('.delAssociation', 'click', function(){
delAssociations($(this).attr('name'));
});
});
4) now you just need to save the associations made by your module autocomplete, and i suggest to perform it by first deleting any association made on a given product and then saving all of them. so you don't have to care about inserting or updating an entry
public function getContent(){
//post process part
if(Tools::isSubmit('saveMyAssociations'){
$product_id = (int)Tools::getValue('productId');
// see the function below, a simple query to delete all the associations on a product
$this->deleteMyAssociations($product_id);
if ($associations = Tools::getValue('inputMyAssociations'))
{
$associations_id = array_unique(explode('-', $associations));
if (count($associations_id))
{
array_pop($associations_id);
//insert all the association we have made.
$this->changeMyAssociations($associations_id, $product_id);
}
}
}
}
protected function deleteMyAssociations($product_id){
return Db::getInstance()->execute('DELETE FROM `'._DB_PREFIX_.'my_associations` WHERE `id_product_1` = '.(int)$product_id);
}
protected function changeMyAssociations($associations_id, $product_id){
foreach ($associations_id as $id_product_2)
Db::getInstance()->insert('my_associations', array(
'id_product_1' => (int)$product_id,
'id_product_2' => (int)$id_product_2
));
}
I hope it can help you to go through all of this.