JSON Store in worklight v6.0 - ibm-mobilefirst

I am trying to integrate the JSONStore standalone app in my multipage application. When I am trying to initialize the collection I am getting the following error "Uncaught TypeError: undefined is not a function". One thing I want to know is, in Worklight version 6.0 I observed that underscore (Lo-Dash) templating was used. But nowhere I found the reference given to the lodash. Also, I didn't find the lodash file anywhere. can anyone please tell me how to do this?
Here is my javascript code
window.$ = window.jQuery = WLJQ;
currentPage = {};
currentPage.init = function(WL, jQuery, lodash) {
alert("current page ::init called");
'use strict';
//Dependencies
var $ = jQuery,
_ = lodash;
//CONSTANTS
var PEOPLE_COLLECTION_NAME = 'people',
KEY_VALUE_COLLECTION_NAME = 'keyvalue',
INIT_FIRST_MSG = 'PERSISTENT_STORE_NOT_OPEN',
NAME_FIELD_EMPTY_MSG = 'Name field is empty',
AGE_FIELD_EMPTY_MSG = 'Age field is empty',
ID_FIELD_EMPTY_MSG = 'Id field is empty',
EMPTY_TABLE_MSG = 'No documents found',
DESTROY_MSG = 'Destroy finished succesfully',
INIT_MSG = 'Collection initialized',
ADD_MSG = 'Data added to the collection',
REPLACE_MSG = 'Document replaced succesfully, call find.',
REMOVE_MSG = 'Documents removed: ',
COUNT_MSG = 'Documents in the collection: ',
CLOSE_ALL_MSG = 'JSONStore closed',
REMOVE_COLLECTION_MSG = 'Removed all data in the collection',
LOAD_MSG = 'New documents loaded from adapter: ',
PUSH_MSG_FAILED = 'Could not push some docs, res: ',
PUSH_MSG = 'Push finished',
PASS_CHANGED_MSG = 'Password changed succesfully';
$('#init').click(initCollection);
$('#destroy').click(destroy);
$('#add-data').click(addData);
$('#find-name').click(findByName);
$('#find-age').click(findByAge);
$('#find-all').click(findAll);
$('#find-id-btn').click(findById);
$('#replace').click(replace);
$('#remove-id-btn').click(removeById);
//Log messages to the console and status field
var _logMessage = function (msg, id) {
//Get reference to the status field
var status = _.isUndefined(id) ? $('div#status-field') : $(id);
//Put message in the status div
status.text(msg);
//Log message to the console
WL.Logger.info(msg);
};
//Show JSONStore document in a table
var _showTable = function (arr) {
if (_.isArray(arr) && arr.length < 1) {
return _logMessage(EMPTY_TABLE_MSG);
}
//Log to the console
WL.Logger.ctx({stringify: true, pretty: true}).info(arr);
var
//Get reference to the status field
status = $('div#status-field'),
//Table HTML template
table = [
'<table id="user_table" >',
'<tr>',
'<td><b>_id</b></td>',
'<td><b>name</b></td>',
'<td><b>age</b></td>',
'<td><b>json</b></td>',
'</tr>',
'<% _.each(people, function(person) { %>',
'<tr>',
'<td> <%= person._id %> </td>',
'<td> <%= person.json.name %> </td>',
'<td><%= person.json.age %></td>',
'<td><%= JSON.stringify(person.json) %></td>',
'</tr>',
'<% }); %>',
'</table>'
].join(''),
//Populate the HTML template with content
html = _.template(table, {people : arr});
//Put the generated HTML table into the DOM
status.html(html);
};
//Scroll to the top every time a button is clicked
$('button').on('click', function () {
$('html, body').animate({scrollTop: 0}, 'slow');
});
function initCollection() {
console.log("init collection method called");
alert("init collection method called");
//Get references to the input fields DOM elements
var usernameField = $('input#init-username'),
passwordField = $('input#init-password');
//Get values from the input fields
var username = usernameField.val() || '',
password = passwordField.val() || '';
//Create the optional options object passed to init
var options = {};
//Check if a username was passed
if (username.length > 0) {
options.username = username;
}
//If if a password was passed
if (password.length > 0) {
options.password = password;
}
//JSONStore collections metadata
var collections = {};
//Define the 'people' collection and list the search fields
collections[PEOPLE_COLLECTION_NAME] = {
searchFields : {name: 'string', age: 'integer'},
//-- Start optional adapter metadata
adapter : {
name: 'people',
add: 'addPerson',
remove: 'removePerson',
replace: 'replacePerson',
load: {
procedure: 'getPeople',
params: [],
key: 'peopleList'
}
}
//-- End optional adapter metadata
};
//Define the 'keyvalue' collection and use additional search fields
collections[KEY_VALUE_COLLECTION_NAME] = {
searchFields : {},
additionalSearchFields : { key: 'string' }
};
//Initialize the people collection
WL.JSONStore.init(collections, options)
.then(function () {
_logMessage(INIT_MSG);
_callEnhanceToAddKeyValueMethods();
})
.fail(function (errorObject) {
_logMessage(errorObject.msg);
});
}
Thanks in advance
regards
V.H.C

I observed that underscore(loadash) templating was used. But nowhere I
found the reference given to the loadash. Also, I didn't find the
loadash file anywhere. can anyone please tell me how to do this?
You can build your own version of lodash or use underscore.
The version of lodash used by worklight is in the WL_ variable. Looking at your code, maybe you want to replace _ = lodash with _ = WL_. Alternatively when you bring your own version lodash or underscore it will be assigned to the _ variable automatically.
Alternatively, you can use other string template libraries like Handlebars.js or basic string interpolation by modifying the String prototype.

Related

Suitescript error: TypeError: Cannot set property "JSZipSync" of undefined

I have the following script where I am getting an error message:
org.mozilla.javascript.EcmaError: TypeError: Cannot set property
"JSZipSync" of undefined to
"org.mozilla.javascript.InterpretedFunction#6e57e155"
(/SuiteScripts/Suitelet to Excel.js#17(eval)#2)
According to Netsuite support, there are no issues with the script and they are not getting this error message
/**
*#NApiVersion 2.x
*#NScriptType Suitelet
*/
define(["N/search", "N/file", "N/https"], function (search, file, https) {
function onRequest(context) {
// Load the xlsx library from the CDN
var response = https.get({
url: "https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js",
});
var script = response.body;
eval(script);
// Replace this with the ID of the saved search you want to run
var searchId = "customsearch_my_saved_search";
// Run the search and get the results
var searchResult = search.load({
id: searchId,
});
var searchRows = searchResult.run().getRange({
start: 0,
end: 1000,
});
// Create an array to hold the data for the Excel file
var data = [];
// Add the column names as the first row
var columnNames = [];
searchResult.columns.forEach(function (column) {
columnNames.push(column.label);
});
data.push(columnNames);
// Add the search results to the data array
searchRows.forEach(function (row) {
var rowData = [];
searchResult.columns.forEach(function (column) {
rowData.push(
row.getValue({
name: column.name,
})
);
});
data.push(rowData);
});
// Create the Excel file
var ws = XLSX.utils.aoa_to_sheet(data);
var wb = XLSX.utils.book_new();
XLSX.utils.book_append_sheet(wb, ws, "Sheet1");
var binaryData = XLSX.write(wb, {
type: "binary",
bookType: "xlsx",
});
var fileName = "search_results.xlsx";
var folderId = -4; // -4 represents the "Home" folder in the file cabinet
var file = file.create({
name: fileName,
fileType: file.Type.EXCEL,
contents: binaryData,
folder: folderId,
});
// Send the file as a response to the user
context.response.writeFile({
file: file,
isInline: true,
});
});
}
return {
onRequest: onRequest,
};
});
I can't tell where I am going wrong?
The JSZipSync is a function in the xlsx.full.min.js library which I believe I have loaded correctly.
I have also tried storing this file in the filing cabinet and referencing that instead of a link to the CDN website.
The same error message is generated in both cases

How to add new fileds/values to line widgets in Barcode mobile view in Odoo14 EE?

Hi i am trying to add two new fields to the Barcode mobile view. I went through the default js code of odoo, but didn't find a way to add my custome fields to it.
Here is the default code
stock_barcode/static/src/js/client_action/lines_widget.js
init: function (parent, page, pageIndex, nbPages) {
this._super.apply(this, arguments);
this.page = page; #don't know where this argument page coming from in argument list.
this.pageIndex = pageIndex;
this.nbPages = nbPages;
this.mode = parent.mode;
this.groups = parent.groups;
this.model = parent.actionParams.model;
this.show_entire_packs = parent.show_entire_packs;
this.displayControlButtons = this.nbPages > 0 && parent._isControlButtonsEnabled();
this.displayOptionalButtons = parent._isOptionalButtonsEnabled();
this.isPickingRelated = parent._isPickingRelated();
this.isImmediatePicking = parent.isImmediatePicking ? true : false;
this.sourceLocations = parent.sourceLocations;
this.destinationLocations = parent.destinationLocations;
// detect if touchscreen (more complicated than one would expect due to browser differences...)
this.istouchSupported = 'ontouchend' in document ||
'ontouchstart' in document ||
'ontouchstart' in window ||
navigator.maxTouchPoints > 0 ||
navigator.msMaxTouchPoints > 0;
},
In _renderLines function,
_renderLines: function () {
//Skipped some codes here
// Render and append the lines, if any.
var $body = this.$el.filter('.o_barcode_lines');
console.log('this model',this.model);
if (this.page.lines.length) {
var $lines = $(QWeb.render('stock_barcode_lines_template', {
lines: this.getProductLines(this.page.lines),
packageLines: this.getPackageLines(this.page.lines),
model: this.model,
groups: this.groups,
isPickingRelated: this.isPickingRelated,
istouchSupported: this.istouchSupported,
}));
$body.prepend($lines);
for (const line of $lines) {
if (line.dataset) {
this._updateIncrementButtons($(line));
}
}
$lines.on('click', '.o_edit', this._onClickEditLine.bind(this));
$lines.on('click', '.o_package_content', this._onClickTruckLine.bind(this));
}
In the above code, you can see this.page.lines field, i need to add my custom two more fields.
Actually it's dead-end for me.
Any solution?

Unable to Upload file(jpeg, png, pdf etc) into File cabinet / custom record through online HTML form in NetSuite

Issue:
I'm facing issue while making a custom record of the online form which I'm getting from Shopify in NetSuite and this happens because I'm unable to upload the attachment or file into the file cabinet.
Tried:
I tried to convert the file or attachment into base64 and also through blob too but unable to get the desired result because Netsuite has limitations that it can't handle more than 1000 characters and this also not a good workaround as the user has the privilege to update the image.
In Code:
Created an online HTML form and map their field with their related field and create a custom record for every record and I'm successfully able to map all the field except attachment(.png, .pdf, .png, etc)
Is there any way to get the result i.e successfully create the custom record through online HTML form or any other workaround to get the result?
For, Now whatever I checked or searched regarding upload image, maybe we are not able to upload an image into their respective field until and unless we save the particular file into the file cabinet.
So, I opt for some other method that is by going through suitelet and then POST the same form after storing the file in the file cabinet into the respective field.
Here, is my workaround code:
var dataBody = JSON.parse(request.getBody());
nlapiLogExecution("debug", "POST BLOCK", 'dataBody = ' + JSON.stringify(dataBody));
var file_folder = '2442342';
var contents = dataBody.imgdata;
var fileType = dataBody.fileType;
var date = new Date();
var newDate = date.getMilliseconds() + date.getTime();
var type = '';
var ext = '';
if ((fileType == 'plain') || (fileType == 'PLAIN')) {
type = 'PLAINTEXT';
ext = 'txt';
}
if ((fileType == 'pdf') || (fileType == 'PDF')) {
type = 'PDF';
ext = 'pdf';
}
if ((fileType == 'png') || (fileType == 'PNG')) {
type = 'PNGIMAGE';
ext = 'png';
}
if ((fileType == 'jpeg') || (fileType == 'JPEG')) {
type = 'JPGIMAGE';
ext = 'jpeg';
}
if ((fileType == 'jpg') || (fileType == 'JPG')) {
type = 'JPGIMAGE';
ext = 'jpg';
}
var name = newDate + '.' + ext;
nlapiLogExecution("debug", "POST BLOCK", 'File Type: ' + type);
try {
if (type) {
var uploadFile = nlapiCreateFile(name, type, contents);
uploadFile.setFolder(file_folder);
var FileId = nlapiSubmitFile(uploadFile);
nlapiLogExecution("debug", "POST BLOCK", 'Image File ID: ' + FileId);
}
if (FileId) {
warr.setFieldValue('custrecord_file_id', FileId);
}
} catch (err) {
nlapiLogExecution("debug", "POST BLOCK", 'Error: ' + err);
}
Hope it helps others. And one more thing you need to decrypt the data as you saving in the file cabinet but if you change the file type to jpg image in all then you may do not need to need to convert it.
Thanks
/**
* #NApiVersion 2.x
* #NScriptType Suitelet
* #NModuleScope SameAccount
*/
define(['N/ui/serverWidget', 'N/record', 'N/runtime', 'N/file', 'N/log'],
function(serverWidget, record, runtime, file, log) {
/**
* Definition of the Suitelet script trigger point.
*
* #param {Object} context
* #param {ServerRequest} context.request - Encapsulation of the incoming request
* #param {ServerResponse} context.response - Encapsulation of the Suitelet response
* #Since 2015.2
*/
function onRequest(context) {
if(context.request.method === 'GET'){
var formObj = serverWidget.createForm({
title: 'Attach Multiple Files'
});
var userId = runtime.getCurrentUser().id;
//User Name
var user = formObj.addField({
id: 'custpage_suitelet_user',
type: serverWidget.FieldType.SELECT,
source: 'employee',
value: userId,
label: 'USER'
});
user.defaultValue = userId;
//File fields
formObj.addField({
id: 'custpage_suitelet_file1',
type: serverWidget.FieldType.FILE,
label: 'File 1'
});
formObj.addField({
id: 'custpage_suitelet_file2',
type: serverWidget.FieldType.FILE,
label: 'File 2'
});
formObj.addField({
id: 'custpage_suitelet_file3',
type: serverWidget.FieldType.FILE,
label: 'File 3'
});
formObj.addSubmitButton({label: 'Submit'});
formObj.addResetButton({label: 'Reset'});
context.response.writePage(formObj);
}
else{
var userId = runtime.getCurrentUser().id;
var file1 = context.request.files['custpage_suitelet_file1'];
var file2 = context.request.files['custpage_suitelet_file2'];
var file3 = context.request.files['custpage_suitelet_file3'];
file1.folder = 624; //folder internal ID
file2.folder = 624; //folder internal ID
file3.folder = 624; //folder internal ID
var id1 = file1.save();
var id2 = file2.save();
var id3 = file3.save();
record.attach({
record: {
type: 'file',
id: id1
},
to: {
type: 'employee',
id: userId
}
});
record.attach({
record: {
type: 'file',
id: id2
},
to: {
type: 'employee',
id: userId
}
});
record.attach({
record: {
type: 'file',
id: id3
},
to: {
type: 'employee',
id: userId
}
});
var formObj = serverWidget.createForm({
title: 'File/s attached!'
});
context.response.writePage(formObj);
}
}
return {
onRequest: onRequest
};
});
I get the script from SuiteAnswers. It's for 2nd version of NS script. The suitelet can be used via an iframe. You could directly attach the file to a the record and/or populate the custrecord_file_id. This it not the best practice if you have limited storage space.
In my case, for return/repair form where the customer should upload a video or an image of the damaged product. I used the "google script" to upload the file into google drive and and I post only the URL to Netsuite via Suitelet.
As far as I can find, the file cabinet is not available to online forms.
As a workaround, you could have Shopify "receive" the file, then code it to send the file to NetSuite via a custom email capture plugin.
Put the right data on the subject line so your capture script knows where to put the file.

knockout.js api search form

The goal of my code is to search on the API search string:
So if you fill out the form you get the hits bij name.
I used the following Knockout.js script:
var viewModel=
{
query : ko.observable("wis"),
};
function EmployeesViewModel(query)
{
var self = this;
self.employees = ko.observableArray();
self.query = ko.observable(query);
self.baseUri = BASE + "/api/v1/search?resource=employees&field=achternaam&q=";
self.apiurl = ko.computed(function() {
return self.baseUri + self.query();
}, self);
//$.getJSON(baseUri, self.employees);
//$.getJSON(self.baseUri, self.employees);
$.getJSON(self.apiurl(), self.employees);
};
$(document).ready(function () {
ko.applyBindings(new EmployeesViewModel(viewModel.query()));
});
The html binding is:
<input type="text" class="search-query" placeholder="Search" id="global-search" data-bind="value: query, valueUpdate: 'keyup'"/>
But if i fill the text box i onley get the default "wis" employees? What am I doing wrong?
Not entirely sure what is wrong here, but have you debugged it and seen what the value of query is in apiurl?
One potential issue is that you are passing employees to getJSON as the observable, not the underlying array, so you could try:
$.getJSON(self.apiurl(), self.employees());
After some digging I found a solution.
var employeesModel = function(){
var self = this;
self.u = base +'/api/v1/search';
self.resource = 'employees';
self.field = 'achternaam';
self.employees = ko.observableArray([]);
self.q = ko.observable();
//Load Json when model is setup
self.dummyCompute = ko.computed(function() {
$.getJSON(self.u,{'resource': self.resource, 'field': self.field, 'q':self.q }, function(data) {
self.employees(data);
});
}, self);
};
ko.applyBindings(new employeesModel());

Custom data source with WinJS?

I am currently implementing a custom data source in a Windows8 application. However, I got some trouble with it: no data is displayed.
First, here is the code:
var dataArray = [
{ title: "Basic banana", text: "Low-fat frozen yogurt", picture: "images/60banana.png" },
// Other data taken from Windows8 ListView quick start
{ title: "Succulent strawberry", text: "Sorbet", picture: "images/60strawberry.png" }
];
var searchAdDataAdapter = WinJS.Class.define(
function () {}, // Constructor
{
itemsFromIndex: function (requestIndex, countBefore, countAfter) {
var that = this;
if (requestIndex >= that._maxCount) {
return WinJS.Promise.wrapError(new WinJS.ErrorFromName(UI.FetchError.doesNotExist));
}
var fetchSize, fetchIndex;
// See which side of the requestIndex is the overlap.
if (countBefore > countAfter) {
// Limit the overlap
countAfter = Math.min(countAfter, 10);
// Bound the request size based on the minimum and maximum sizes.
var fetchBefore = Math.max(
Math.min(countBefore, that._maxPageSize - (countAfter + 1)),
that._minPageSize - (countAfter + 1)
);
fetchSize = fetchBefore + countAfter + 1;
fetchIndex = requestIndex - fetchBefore;
} else {
countBefore = Math.min(countBefore, 10);
var fetchAfter = Math.max(Math.min(countAfter, that._maxPageSize - (countBefore + 1)), that._minPageSize - (countBefore + 1));
fetchSize = countBefore + fetchAfter + 1;
fetchIndex = requestIndex - countBefore;
}
// Create an array of IItem objects:
// results =[{ key: key1, data : { field1: value, field2: value, ... }}, { key: key2, data : {...}}, ...];
for (var i = 0, itemsLength = dataArray.length ; i < itemsLength ; i++) {
var dataItem = dataArray[i];
results.push({
key: (fetchIndex + i).toString(),
data: dataArray[i]
});
}
// Get the count.
count = dataArray.length;
return {
items: results, // The array of items.
offset: requestIndex - fetchIndex, // The index of the requested item in the items array.
totalCount: count
};
},
getCount: function () {
return dataArray.length;
}
}
);
var searchAdDataSource = WinJS.Class.derive(WinJS.UI.VirtualizedDataSource, function () {
this._baseDataSourceConstructor(new searchAdDataAdapter());
});
// Create a namespace to make the data publicly
// accessible.
var publicMembers = {
itemList: new searchAdDataSource()
};
WinJS.Namespace.define("DataExample", publicMembers);
I know the code is a little bit long, but the major part of it is taken from official Microsoft custom data source quick start.
I tried to debug it, but it seems the code contained in itemFromIndex is never used (my breakpoint is never reached).
The HTML code is:
<div id="basicListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : DataExample.itemList.dataSource}">
</div>
I do not use any template for the moment, to simplify the code as more as I can. Data are normally displayed in text this way (but nothing appears).
Have one of this great community any idea?
Furthermore, I do not understand the countBefore and countAfter parameters, even with the documentation. Can somebody explain it to me with other words?
Thanks a lot! :)
Try modifying your HTML code to the following:
<div id="basicListView" data-win-control="WinJS.UI.ListView"
data-win-options="{itemDataSource : DataExample.itemList}">
</div>
No need to call the .datasource member, as you are talking to the datasource directly.