Export to CSV issue - html
Hi I am trying to export dynamic table in the form of csv file. I am facing two issue
Only the first page of the table is getting displayed in the exported file. As it is a dynamically filled table there can be multiple pages. I am allowing 10 items per page.
The file that is getting exported is not in csv format . It is in some default file format.
If somebody could help. Let me know if you need any other details:
Code:
function exportTableToCSV() {
var tab = $('#searchObjectTableTabs').tabs('getSelected');// selecting the table
var tabIndex = $('#searchObjectTableTabs').tabs('getTabIndex', tab);
var data;
var rows;
if (tabIndex == '0') // first index of the tab under which the table will be displayed
{
data = $('#dg').first(); //Only one table
rows = $('#dg').datagrid('getRows');
} else if (tabIndex == '1') // second index
{
data = $('#doc').first(); //Only one table
rows = $('#doc').datagrid('getRows');
}
var csvData = [];
var tmpArr = [];
var tmpStr = '';
data.find("tr").each(function () {
if ($(this).find("th").length) {
$(this).find("th").each(function () {
tmpStr = $(this).text().replace(/"/g, '""');
tmpArr.push('"' + tmpStr + '"');
});
csvData.push(tmpArr);
}
tmpArr = [];
$.each(exportArray, function (index, value) {
csvData.push(exportArray[index].ID + "," + exportArray[index].itemrev + "," + exportArray[index].type + "," + exportArray[index].status + "," + exportArray[index].desc + "," + exportArray[index].owner + "," + exportArray[index].ogrp);
});
csvData.push(tmpArr.join('\n'));
// printObject(tmpArr);
});
var output = csvData.join('\n');
var uri = 'data:application/csv;charset=UTF-8,' + encodeURIComponent(output);
window.open(uri);
}
Please try this one:
$(document).ready(function () {
function exportTableToCSV($table, filename) {
var $rows = $table.find('tr:has(td)'),
// Temporary delimiter characters unlikely to be typed by keyboard
// This is to avoid accidentally splitting the actual contents
tmpColDelim = String.fromCharCode(11), // vertical tab character
tmpRowDelim = String.fromCharCode(0), // null character
// actual delimiter characters for CSV format
colDelim = '","',
rowDelim = '"\r\n"',
// Grab text from table into CSV formatted string
csv = '"' + $rows.map(function (i, row) {
var $row = $(row),
$cols = $row.find('td');
return $cols.map(function (j, col) {
var $col = $(col),
text = $col.text();
return text.replace(/"/g, '""'); // escape double quotes
}).get().join(tmpColDelim);
}).get().join(tmpRowDelim)
.split(tmpRowDelim).join(rowDelim)
.split(tmpColDelim).join(colDelim) + '"',
// Data URI
csvData = 'data:application/csv;charset=utf-8,' + encodeURIComponent(csv);
$(this)
.attr({
'download': filename,
'href': csvData,
'target': '_blank'
});
}
// This must be a hyperlink
$(".export").on('click', function (event) {
// CSV
exportTableToCSV.apply(this, [$('#dvData>table'), 'export.csv']);
// IF CSV, don't do event.preventDefault() or return false
// We actually need this to be a typical hyperlink
});
});
Demo
Thanks for the answer. I have done some editing and now I am able to retrieve the csv file. However I am only getting the values for the first page. For ex: If I have 20 pages in my table grid. the export is happening only for the first page
Is it because of this part of the code: Can we have a different syntax
{
data = $('#dg').first(); //Only one table
rows = $('#dg').datagrid('getRows');
}
Full Code:
function exportTableToCSV(filename) {
var tab = $('#searchObjectTableTabs').tabs('getSelected');// selecting the table
var tabIndex = $('#searchObjectTableTabs').tabs('getTabIndex', tab);
var data;
var rows;
alert('inside');
if (tabIndex == '0') // first index of the tab under which the table will be displayed
{
data = $('#dg').first(); //Only one table
rows = $('#dg').datagrid('getRows');
} else if (tabIndex == '1') // second index
{
data = $('#doc').first(); //Only one table
rows = $('#doc').datagrid('getRows');
}
var csvData = [];
var tmpArr = [];
var tmpStr = '';
data.find("tr").each(function ()
{
if ($(this).find("th").length) {
$(this).find("th").each(function () {
tmpStr = $(this).text().replace(/"/g, '""');
tmpArr.push('"' + tmpStr + '"');
});
csvData.push(tmpArr);
}
tmpArr = [];
$.each(exportArray, function (index, value)
{
csvData.push(exportArray[index].type + "," + exportArray[index].status + "," + exportArray[index].ID + "," + exportArray[index].itemrev + "," + exportArray[index].desc + "," + exportArray[index].owner + "," + exportArray[index].ogrp);
});
csvData.push(tmpArr.join('\n'));
// printObject(tmpArr);
});
alert('before this');
var output = csvData.join('\n');
csvData = 'data:application/csv;charset=utf-8,' + encodeURIComponent(output);
$(this)
.attr({
'download': filename,
'href': csvData,
'target': '_blank'
});
alert('done');
}
$(".export").on('click', function (event) {
// CSV
exportTableToCSV.apply(this,['export.csv']);
});
Related
find all label and product in json python
I'm trying to find all label and product in text/javascript, but I don't have any idea to how do it. I'm trying to parse label, id and products. var spConfigDisabledProducts = [-1 , '290058', '290060', '290061', '290062', '290063', '290065', '290071', '290073', '290075', '290076', '290077', '290078' ]; var spConfig = new Product.Config({"attributes":{"959":{"id":"959","code":"aw_taglia","label":"Taglia","options":[{"id":"730","label":"36 ½","price":"0","oldPrice":"0","products":["290058"]},{"id":"731","label":"37 ½","price":"0","oldPrice":"0","products":["290060"]},{"id":"732","label":"38","price":"0","oldPrice":"0","products":["290061"]},{"id":"733","label":"38 ½","price":"0","oldPrice":"0","products":["290062"]},{"id":"734","label":"39","price":"0","oldPrice":"0","products":["290063"]},{"id":"735","label":"40","price":"0","oldPrice":"0","products":["290064"]},{"id":"736","label":"40 ½","price":"0","oldPrice":"0","products":["290065"]},{"id":"737","label":"41","price":"0","oldPrice":"0","products":["290066"]},{"id":"738","label":"42","price":"0","oldPrice":"0","products":["290067"]},{"id":"739","label":"42 ½","price":"0","oldPrice":"0","products":["290068"]},{"id":"740","label":"43","price":"0","oldPrice":"0","products":["290069"]},{"id":"741","label":"44","price":"0","oldPrice":"0","products":["290070"]},{"id":"742","label":"44 ½","price":"0","oldPrice":"0","products":["290071"]},{"id":"743","label":"45","price":"0","oldPrice":"0","products":["290072"]},{"id":"744","label":"45 ½","price":"0","oldPrice":"0","products":["290073"]},{"id":"745","label":"46","price":"0","oldPrice":"0","products":["290074"]},{"id":"746","label":"47","price":"0","oldPrice":"0","products":["290075"]},{"id":"747","label":"47 ½","price":"0","oldPrice":"0","products":["290076"]},{"id":"748","label":"13.5","price":"0","oldPrice":"0","products":["290077"]},{"id":"749","label":"48 ½","price":"0","oldPrice":"0","products":["290078"]}]}},"template":"#{price}\u00a0\u20ac","basePrice":"130","oldPrice":"130","productId":"290059","chooseText":"Seleziona","taxConfig":{"includeTax":true,"showIncludeTax":true,"showBothPrices":false,"defaultTax":0,"currentTax":0,"inclTaxTitle":"Incl. Tasse"}}); jQuery("#attribute959 option").each(function () { var option = jQuery(this); var id = option.attr('value'); jQuery.each(spConfig.config.attributes, function () { jQuery.each(this.options, function () { if (this.id == id) { if (spConfigDisabledProducts.indexOf(this.products[0]) >= 0) { option.data('disabled', true); } } }); }); });
It's possible to do that less elegantly but without regex - just a series of splits and clean ups: import json code = [your script above] code2 = html.replace('½','').split('":"Taglia","options":[{')[1].split('}]}},"template"')[0].split('},{') for i in range(len(code2)): data = json.loads('{'+code2[i]+'}') print(data['id'],data['label']) Output: 730 36 731 37 732 38 733 38 etc.
You can regex out javascript object and pass to json then parse out info import re import json #html = response.content from requests html = ''' var spConfigDisabledProducts = [-1 , '290058', '290060', '290061', '290062', '290063', '290065', '290071', '290073', '290075', '290076', '290077', '290078' ]; var spConfig = new Product.Config({"attributes":{"959":{"id":"959","code":"aw_taglia","label":"Taglia","options":[{"id":"730","label":"36 ½","price":"0","oldPrice":"0","products":["290058"]},{"id":"731","label":"37 ½","price":"0","oldPrice":"0","products":["290060"]},{"id":"732","label":"38","price":"0","oldPrice":"0","products":["290061"]},{"id":"733","label":"38 ½","price":"0","oldPrice":"0","products":["290062"]},{"id":"734","label":"39","price":"0","oldPrice":"0","products":["290063"]},{"id":"735","label":"40","price":"0","oldPrice":"0","products":["290064"]},{"id":"736","label":"40 ½","price":"0","oldPrice":"0","products":["290065"]},{"id":"737","label":"41","price":"0","oldPrice":"0","products":["290066"]},{"id":"738","label":"42","price":"0","oldPrice":"0","products":["290067"]},{"id":"739","label":"42 ½","price":"0","oldPrice":"0","products":["290068"]},{"id":"740","label":"43","price":"0","oldPrice":"0","products":["290069"]},{"id":"741","label":"44","price":"0","oldPrice":"0","products":["290070"]},{"id":"742","label":"44 ½","price":"0","oldPrice":"0","products":["290071"]},{"id":"743","label":"45","price":"0","oldPrice":"0","products":["290072"]},{"id":"744","label":"45 ½","price":"0","oldPrice":"0","products":["290073"]},{"id":"745","label":"46","price":"0","oldPrice":"0","products":["290074"]},{"id":"746","label":"47","price":"0","oldPrice":"0","products":["290075"]},{"id":"747","label":"47 ½","price":"0","oldPrice":"0","products":["290076"]},{"id":"748","label":"13.5","price":"0","oldPrice":"0","products":["290077"]},{"id":"749","label":"48 ½","price":"0","oldPrice":"0","products":["290078"]}]}},"template":"#{price}\u00a0\u20ac","basePrice":"130","oldPrice":"130","productId":"290059","chooseText":"Seleziona","taxConfig":{"includeTax":true,"showIncludeTax":true,"showBothPrices":false,"defaultTax":0,"currentTax":0,"inclTaxTitle":"Incl. Tasse"}}); jQuery("#attribute959 option").each(function () { var option = jQuery(this); var id = option.attr('value'); jQuery.each(spConfig.config.attributes, function () { jQuery.each(this.options, function () { if (this.id == id) { if (spConfigDisabledProducts.indexOf(this.products[0]) >= 0) { option.data('disabled', true); } } }); }); });''' p = re.compile(r'Product\.Config\((.*?)\)', re.DOTALL) data = json.loads(p.findall(html)[0]) for attribute in data['attributes']: print('-----------------attribute--------------') print('label = ' + data['attributes'][attribute]['label'], 'id = ' + data['attributes'][attribute]['id']) print('-----------------options----------------') for product in data['attributes'][attribute]['options']: print('label = ' + product['label'], 'id = ' + product['id'], 'product = ' + product['products'][0])
How to prevent my github page from making post title 'title-cased'
I have created a github page and have chosen one of proposed Jekyll themes called minima. To add a post I have created a file called 2018-11-16-My-first-post-on-github.md. However, the post title displayed is a text converted to title case: My First Post On Github, so every first letter in each word is made upper case. How can I prevent that? Is this theme-dependent?
exports.toTitleCase = function(str){ var smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|vs?\.?|via)$/i; return (str+'').replace(/[A-Za-z0-9\u00C0-\u00FF]+[^\s-]*/g, function(match, index, title){ if (index > 0 && index + match.length !== title.length && match.search(smallWords) > -1 && title.charAt(index - 2) !== ":" && (title.charAt(index + match.length) !== '-' || title.charAt(index - 1) === '-') && title.charAt(index - 1).search(/[^\s-]/) < 0) { return match.toLowerCase(); } if (match === 'tus') { return match; } if (match.substr(1).search(/[A-Z]|\../) > -1) { return match; } // Avoid uppercasing 'mod_deflate', apt-file - kvz if (match.match(/.[\_\-\/\d]./)) { return match; } // Avoid uppercasing '`frame`', '/sftp/import' - kvz if (match.match(/(^[`\/]|[`]$)/)) { return match; } // Avoid uppercasing: 'tmpfs' or anything that doesn't have a vowel - kvz if (!match.match(/[aeiou]/)) { return match; } return match.charAt(0).toUpperCase() + match.substr(1); }); }; exports.newPost = function (content, opts, cb){ var self = this; var matches = []; var oldTitle = ''; var newTitle = ''; var oldLine = ''; var heading = ''; var newLine = ''; var changes = []; var words = []; var frontMatter = content.split('---')[1]; if (frontMatter) { matches = frontMatter.match(/^(title\s*:\s*)\"?(.+?)\"?[\ \t]*$/im); oldTitle = matches[2]; newTitle = self.toTitleCase(oldTitle).trim(); oldLine = matches[0]; newLine = matches[1] + '"' + newTitle + '"'; if (oldLine !== newLine) { changes.push({oldTitle: oldTitle, newTitle: newTitle}); content = content.replace(oldLine, newLine); } } if (opts.body === true) { matches = content.match(/^\#{1,6} ([a-zA-Z0-9\-\;\!\?\%\&\;\:\.\/\(\)\ ]+)$/mg) for (var i in matches) { words = matches[i].split(' ') heading = words.shift(); oldTitle = words.join(' '); newTitle = self.toTitleCase(oldTitle).trim(); oldLine = heading + ' ' + oldTitle; newLine = heading + ' ' + newTitle; if (oldLine !== newLine) { changes.push({oldTitle: oldTitle, newTitle: newTitle}); content = content.replace(oldLine, newLine); } } } if (changes.length === 0) { content = null; } return cb(null, content, changes); };
Jekyll automatically converts your post title to uppercase upon import. You can fix this by using a plugin, or by using a YAML file or Front matter to specify your title value directly.
NodeJS creating JSON using all JSONs uploaded by user
I am trying to make a JSON file using all the JSON files in a directory. Every time a user uploads a new JSON a new combined JSON should be generated. I want the new JSON to have a custom structure hence cant use any libraries. I have the following code: router.post('/upload', function(req, res) { var sampleFile; var bbbid = req.body.bbbid; DDLFile = req.files.DDLFile; j++; DDLFile.mv('/uploads/' + bbbid + '/device' + j + '.json', function (err) { if (err) { res.status(500).send(err); } else { res.redirect("fileuploaded"); } }); var myfiles = []; var fs = require('fs'); var arrayOfFiles = fs.readdirSync('/uploads/' + bbbid); arrayOfFiles.forEach(function (file) { myfiles.push(file); console.log(myfiles); }); console.log('No of Files:', myfiles.length); var files = myfiles.length; console.log('Files:', files); console.log('J', j); var cddl = "{ BBBID:" + bbbid; if (files == 0) { cddl = cddl + '}'; console.log('Entered if loop'); } else { var i = 0; /*var obj; fs.readFile('/uploads/' + bbbid + '/device' + j + '.json', 'utf8', function (err, data) { if (err) throw err; obj = JSON.parse(data); });*/ for (i = 0; i < files; i++) { console.log('Entered For loop'); console.log('Count:', count); console.log('Sensor:', sensor); try{ var obj = fs.readFileSync('/uploads/' + bbbid + '/device' + count + '.json', 'utf8');} catch(err){ console.log(err); } console.log('everything good'); var obj1 = JSON.parse(obj); console.log('hi'); //JSON.stringify(obj); var ddl = require('/uploads/' + bbbid + '/device' + count + '.json'); console.log('o'); cddl = cddl + ", {" + obj1.DDL.Sensor.Description.Verbose_Description + ":" + JSON.stringify(ddl) + "}" JSON.stringify(cddl); console.log(cddl); count++; sensor++; console.log('Count:', count); console.log('Sensor:', sensor); } cddl = cddl + '}'; JSON.stringify(cddl); console.log(cddl); } }); I want to generate a new cddl everytime a new file is uploaded. Having a lot of problems. Help please!
I see two problems. First instead of this: var obj = fs.readFileSync('/uploads/' + bbbid + '/device' + count + '.json', 'utf8');} catch(err){ console.log(err); } console.log('everything good'); var obj1 = JSON.parse(obj); You can write(fix path, if necessary): var obj1 = require('./uploads/' + bbbid + '/device' + count + '.json') Then, when you call: JSON.stringify(cddl); You're not saving the result anywhere. So you should save it in the place, you need to: var a = JSON.stringify(cddl); And when all set, dont forget to write to file back using fs.writeFileSync or async one fs.writeFile.
Edit JSON Response in AngularJS and bind to the list
Hi I know how to read form json and bind the output to the view however I would like to add some logic into the output and bid converted data. How do I output my forEach to the array and than do ng-repeat based on it or combine my ajax data binding with my amendments? At the moment if I change $scope.fleet = newData; => $scope.fleet = data; and view.html eg. {{item.name}} everything works but I would like to add some changes to the name before binding. My code: controler.js function LoadFleetControler($scope){ $.ajax({ url: 'https://someapi/list', type: 'GET', dataType: 'json', success: function (data) { var newData = []; angular.forEach(data, function(value, key){ /* ############################ Options ############################ */ var d = new Date(); var month = d.getMonth() + 1; var thisDate = d.getDate() + '/' + padLeft(month,2) + '/' + d.getFullYear(); var thisCycle = dateToDays(thisDate, value.end_bill_date) + 1; // Include last 24H /* ############################ Scope ############################ */ $scope.fleetUser = value.name; $scope.fleetCycle = 'Cycle: ' + thisCycle + ' days left (' + value.end_bill_date + ')'; $scope.fleetPercentageUsed = value.percentage_used; $scope.fleetCycleColor = highlighSwitch(value.percentage_used); }, newData); console.log(newData); $scope.fleet = newData; $scope.$apply(); }, error: function(data) { $scope.error = true; $scope.$apply(); } }); } view.html <div ng-controller="LoadFleetControler"> <ons-list> <ons-list-item ng-show="error">Server Connection Error</ons-list-item> <ons-list-item class="topcoat-list__item__line-height" ng-repeat="item in fleet"> {{fleetUser}} <small>{{fleetCycle}}</small> </ons-list-item> </ons-list> </div>
Inside angular.forEach(), you are assigning items to the scope, when you probably meant to create new objects and add them to the newData array... angular.forEach(data, function(value, key){ // ... var newItem = {}; newItem.fleetUser = value.name; newItem.fleetCycle = 'Cycle: ' + thisCycle + ' days left (' + value.end_bill_date + ')'; newItem.fleetPercentageUsed = value.percentage_used; newItem.fleetCycleColor = highlighSwitch(value.percentage_used); newData.push(newItem); } );
multiple async mongo request generate messed up returns
I'm trying to build a JSON out of multiple requests on my mongodb. since I'm not using DBRef, I have to build the "table joints" by myself, and that's how I ended up in this mess. This is the code that is giving me the headaches from a couple of days now. (the mongo part is done with mongoskin) var getUserFeed = function(thelimit, out) { userfeed = db.collection("userfeed"); apparel = db.collection("apparel"); store = db.collection("stores"); if(thelimit) args = {limit:thelimit, sort: [['date',-1]]}; userfeed.find({},args).toArray(function(e, feed) { if (e) console.log("error: ", e); // gather aparel infos var i=0; var ret_feeds = []; feed.forEach(function(cur_feed) { var outfits=[]; console.log("beginning with: " + cur_feed.url); var resfeed = ""; resfeed = cur_feed; resfeed.url = baseurl + snapurl + resfeed.url + "_small.jpg"; i=0; cur_feed.apparel_ids.forEach(function(item) { /*>>*/ apparel.find({"_id": item},{limit:1}).toArray(function(e, results) { console.log(">>>>>>>>>>> APPAREL_FIND { i:" + i + "}"); if (e) console.log("error: ", e); results = results[0]; if(results.apparel_cat == 1) url_subcat = "pants/"; else if(results.apparel_cat == 2) url_subcat = "shirts/"; else if(results.apparel_cat == 2) url_subcat = "tshirts/"; results.thumb = baseurl + outfiturl + url_subcat + results.apparel_id + "/front.jpg"; results.size = "M"; ///// TOBE REAL VERY SOON results.gallery = [ baseurl + outfiturl + url_subcat + results.apparel_id + "/model.jpg", baseurl + outfiturl + url_subcat + results.apparel_id + "/front.jpg" ]; outfits.push(results); // quick and dirty, 2 b refined.. i++; if(i>=cur_feed.apparel_ids.length) { // pack it up // resfeed.url = resfeed.url; resfeed.outfits = outfits; resfeed.fav = false; resfeed.bough = false; // retrieve store infos /*>>>*/ store.find({"_id":resfeed.store_id}, {limit: 1}).toArray(function(e, resstore) { console.log("\t############# STORE_FIND { i:" + i + "}"); if (e) console.log("error: ", e); resfeed.store = resstore[0]; resfeed.store.class = "hem"; ret_feeds.push(resfeed); if(ret_feeds.length >= feed.length) { console.log("\t\t######################calling return [ ret_feeds.length = " + ret_feeds.length + " feed.length = " + feed.length); out.send(ret_feeds); } }); } }); }); }); }); } This code fails, because returns the json before finishing its task, so the next time that it tries to return another json it crashes miserably due to the fact the the headers have already been sent. Now as you can see, I have 3 collections: userfeed, apparel and stores. the goal of this function is to retrieve all the items in the userfeed collection, extract the outfits (based on the outfit_id array that is part of the userfeed collection), and also extract the store infos related in the same way to each userfeed entry, like so: I know that async.js or equivalent is the way to go: I've red like a gazillion of other posts here on SO, but I still can't get my head around it, probably because the whole mechanism behind the async.js or flow control in general it's still out of focus in my mind. I'm still a noob at node :) UPDATE I think I found the right path for understanding here: http://www.sebastianseilund.com/nodejs-async-in-practice this guy made a terrific job in describing use-case by use-case all the ways to apply async.js to your code. I'll post the solution as soon as I get around it. UPDATE 2 Thanks to the above dude I could work out a working solution, below is the answer.
After so much struggling I have finally managed to get a solution. async.js was the answer as I was (obviously) suspecting. FYI here's the working code. If you like to point out improvements or anything else, you are more than welcome var getUserFeed = function(thelimit, out) { userfeed = db.collection("userfeed"); apparel = db.collection("apparel"); store = db.collection("stores"); var args; if(thelimit) args = {limit:thelimit, sort: [['date',-1]]}; var outfits=[]; var feeds = array(); async.series([ // userfeed find function(callback) { userfeed.find({},args).toArray(function(e, feed) { if(e) callback(e); feeds = array(feed); console.log(feeds.length + " retrieved. stepping in"); callback(null, null); }); }, // join function(callback) { async.forEach(feeds, function(thefeed, callback) { var i = feeds.indexOf(thefeed); async.parallel([ // load apparel infos function(callback) { console.log("\t >>> analyzing thefeed id " + thefeed._id); async.forEach(thefeed.apparel_ids, function(apparel_id, callback) { apparel.find({"_id": apparel_id},{limit:1}).toArray(function(e, results) { if (e) console.log("error: ", e); results = results[0]; if(results.apparel_cat == 1) url_subcat = "pants/"; else if(results.apparel_cat == 2) url_subcat = "shirts/"; else if(results.apparel_cat == 2) url_subcat = "tshirts/"; results.thumb = baseurl + outfiturl + url_subcat + results.apparel_id + "/front.jpg"; results.size = "M"; ///// TOBE REAL VERY SOON results.gallery = [ baseurl + outfiturl + url_subcat + results.apparel_id + "/model.jpg", baseurl + outfiturl + url_subcat + results.apparel_id + "/front.jpg" ]; console.log("\t\t### pushing data into thefeed_index: " + i); if(!util.isArray(feeds[i].oufits)) feeds[i].outfits = array(); feeds[i].outfits.push(results); callback(null, null); }); }, callback); }, // load store infos function(callback) { store.find({"_id":thefeed.store_id}, {limit: 1}).toArray(function(e, resstore) { console.log("\t### STORE_FIND"); if (e) console.log("error: ", e); feeds[i].store = resstore[0]; feeds[i].store.class = "hem"; callback(null, null); }); } ], callback); }, callback); } // MAIN ], function(err, result) { console.log("feed retrieval completed. stepping out"); if (err) return next(err); out.send(feeds); }); };