Raphaeljs: Copy element or set to another paper - svg

Is there a possibility of adding an element or set that exists within a paper to another paper, without creating each element twice from scratch?
Background for this: I visualize a large node graph and want to create an "overview map" in a separate paper.

The following set of codes adds a nw function to Raphael Set and Raphael Elements. The usage is to simply call .cloneToPaper(targetPaper) on any set or element.
(function (R) {
var cloneSet; // to cache set cloning function for optimisation
/**
* Clones Raphael element from one paper to another
*
* #param {Paper} targetPaper is the paper to which this element
* has to be cloned
*
* #return RaphaelElement
*/
R.el.cloneToPaper = function (targetPaper) {
return (!this.removed &&
targetPaper[this.type]().attr(this.attr()));
};
/**
* Clones Raphael Set from one paper to another
*
* #param {Paper} targetPaper is the paper to which this element
* has to be cloned
*
* #return RaphaelSet
*/
R.st.cloneToPaper = function (targetPaper) {
targetPaper.setStart();
this.forEach(cloneSet || (cloneSet = function (el) {
el.cloneToPaper(targetPaper);
}));
return targetPaper.setFinish();
};
}(Raphael));
For a sample implementation, you may check out this fiddle: http://jsfiddle.net/shamasis/39yTS/
Note that if you have events on the source elements, they will not be cloned to the target paper.

Raphael don't allow to move element from one paper to another directly.
So it is better to create a new element with same property in the target paper.
I have created following sample method. you can add the code in your page and use cloneToPaper function to clone a element or a set to another paper.
function extractJSON(element) {
var attr = element.attr(),
newNodeJSON = {type: element.type},
key;
for (key in attr) {
newNodeJSON[key] = attr[key];
}
return newNodeJSON;
}
/*
* #param {Object} element: raphael element or set
* #param {Object} paper: Target paper where to clone
* #return {object}: newly created set or element
*/
function cloneToPaper(element, paper) {
var isSet = element.type === 'set',
elementJSONArr = [],
i, ln, newSet;
if (isSet) {
ln = element.items.length;
for (i = 0; i < ln; i += 1) {
elementJSONArr.push(extractJSON(element.items[i]));
}
}
else {
elementJSONArr.push(extractJSON(element));
}
newSet = paper.add(elementJSONArr);
return isSet ? newSet : newSet[0];
}

Related

On an afterSubmit when we creating a copy of one inventory item (name with '-c') ,The original ID of item link should come in a field on a copy order

I tried this above, here I am getting a null value only from my previous record.
Kindly give some guidance to solve my questions.
thanks in advance.
/**
*#NApiVersion 2.0
*#NScriptType UserEventScript
*/
define(["N/url", "N/record", "N/runtime"], function (url, record, runtime) {
function afterSubmit(context){
var recordobj = context.newRecord;
var prevItemrecord= context.oldRecord;
var Itemname = recordobj.getValue({fieldId:'itemid'});
var prevItemname = prevItemrecord.getValue({fieldId : 'itemid'});
var Type=context.type;
var checkbox=recordobj.getValue({fieldId:'custitem17'});
if(Type== context.UserEventType.CREATE)
if((Itemname=prevItemname+'-c')&&(checkbox=true))
record.submitFields({
type: recordobj.type,
id: recordobj.id,
values:{custitem_item_link:prevItemname}
});
}
return{
afterSubmit:afterSubmit
}
});
This is my code
On create there is no old record.
Since you are trying to update the same record as was just created you are better off having this in a beforeSubmit event script
if((Itemname=prevItemname+'-c')&&(checkbox=true)) this is an error
if((Itemname == prevItemname+'-c') && checkbox) is more what you need
If you are trying to capture a copy operation you can set that up in the beforeLoad event that you use in the beforeSubmit event.
function beforeLoad(ctx){
if(ctx.type == ctx.UserEventType.COPY){
if(ctx.form){
ctx.form.addField({
id:'custpage_original_item',
label:'Copied Item',
type:ui.FieldType.SELECT,
source:'item'
}).updateDisplayType({
displayType:ui.FieldDisplayType.HIDDEN
}).defaultValue = ctx.request.parameters.id;
// your naming makes me wonder if you are trying to link to the
// source item rather than just saving a reference to the source
// item's name
/*
* Using the original item's name like below is closer to what you
* posted but I think by the time the script runs the itemid field
* has been cleared.
* ctx.form.addField({
* id:'custpage_original_item_name',
* label:'Copied Item Name',
* type:ui.FieldType.TEXT
* }).updateDisplayType({
* displayType:ui.FieldDisplayType.HIDDEN
* }).defaultValue = ctx.newRecord.getValue({fieldId:'itemid'});
*/
}
}
}
function beforeSubmit(ctx){
if(ctx.type == ctx.UserEventType.CREATE){
const itemRec = ctx.newRecord;
if(itemRec.getValue({fieldId:'custitem17'})){
const sourceId = itemRec.getValue({fieldId:'custpage_original_item'})
if(sourceId){
itemRec.setValue({
fieldId:'custitem_item_link:prevItemname',
value:sourceId
});
/* or use a search function to look up the original item's name
* and then test the new item's name.
*/
}
}
}
}

"SuiteScript 2.0 entry point scripts must implement one script type function" Error

I'm trying to upload this code to NetSuite
/**
* #NApiVersion 2.0
* #NScriptType ClientScript
* #NModuleScope SameAccount
*/
define(['N/ui/dialog'],
function(dialog){
/**
* Validation function to be executed when sublist line is committed.
*
* #param {Object} context
* #param {Record} context.currentRecord - Current form record
* #param {string} context.sublistId - Sublist name
*
* #returns {boolean} Return true if sublist line is valid
*
* #since 2015.2
*/
function validadeRate(context){
try{
var currentRecord = context.currentRecord
var sublistName = context.sublistId
if(sublistname ==='expense'){
var categ = CurrentRecord.getCurrentSublistValue({
sublistId: sublistName,
fieldId: 'category'
})
if ((categ = 259) && (rate != 0.819)){
var currIndex = currentRecord.getCurrentSublistIndex({
sublistId: sublistName
})
currIndex +=1
var options = {
title : 'Rate Incorreto!',
message:'Por favor, verifique o valor informado no campo Rate na linha ' + currIndex + '.',
}
dialog.alert(options).then(function (result) { }).catch(function(result){})
return false
}
}
return true
}
catch(ex){
log.error('validateLine: ', ex.message)
}
}
return {
validadeRate : validadeRate
}
});
But I'm getting this error when I'm trying to upload to file to Netsuite:
Notice
SuiteScript 2.0 entry point scripts must implement one script type function.*
This is part of a function that will validade the rate for one expense category.
How can I solve this?
thanks in advance!
This is NetSuite's 'Entry Point Script Validation' saying that the script is invalid because it doesn't include one of the predefined entry point (event) functions. These functions are:
fieldChanged
lineInit
pageInit
postSourcing
saveRecord
sublistChanged
validateDelete
validateField
validateInsert
validateLine
You can work around this validation and upload the script by adding one of those entry points, even if it does nothing. For example, inside your function (dialog) function you can add a pageInit() function:
function pageInit(scriptContext) {}
and change your return block to:
return {
validadeRate : validadeRate,
pageInit: pageInit
}
Now it has a valid entry point and the validation should pass.
However, there may be an even easier way. It appears (going by the JSDoc block), that your validadeRate function is supposed to be triggered each time a sublist line is added. This is exactly what the validateLine entry point is for. So you could just change the key in your return block to "validateLine"
return {
validateLine: validadeRate
}
and NetSuite would know to call validadeRate each time a line is added.
You have specified this as a Client Script module, but have not assigned a handler to any of the Client Script entry points. Read the Help document SuiteScript 2.0 Client Script Entry Points and API, and implement any one of the entry points in your module.
Change return function as below. and test once.
return
{
validateLine : validadeRate
}

Netsuite: how to add a custom link to the Nav Bar or Header

Is there any way to customize the Nav Bar or the Header to have a custom link?
The use-case is that I have a JIRA issue collector that is driven by javascript. I would like the user to provide feedback from the page they are having issues. However, any solution I can come up with so far takes the user away from the current page.
Example of what I have that takes the user away:
I currently have a Suitelet that is in one of the menus. That Suitelet invokes javascript but even then the user is taken away.
I have a workflow on the case record that calls some Javascript Javascript in one of the UI-based action's conditions is invoked. Similar to #1 but on the case record.
I'm thinking I'm going to need to create and public a chrome extension for my company's domain just to get a pervasive bit of javascript to run for all pages...seems like a sledgehammer.
I hope someone can prove me wrong, but as far as I am aware there is no way to natively inject Javascript or anything into the NetSuite header/navbar - they don't offer customisation to the header/navbar.
I've resorted to creating a Userscript that I load through the Violent Monkey extension for Chrome or Firefox.
Example Userscript Template
// ==UserScript==
// #name NetSuite Mods (Example)
// #namespace Violentmonkey Scripts
// #match *.netsuite.com/*
// #include *.netsuite.com/*
// #grant GM_addStyle
// #version 1.0
// #author Kane Shaw - https://stackoverflow.com/users/4561907/kane-shaw
// #description 6/11/2020, 6:25:20 PM
// ==/UserScript==
// Get access to some commonly used NLAPI functions without having to use "unsafeWindow.nlapi..." in our code
// You can add more of these if you need access to more of the functions contained on the NetSuite page
nlapiSetFieldText = unsafeWindow.nlapiSetFieldText;
nlapiSetFieldValue = unsafeWindow.nlapiSetFieldValue;
nlapiGetFieldText = unsafeWindow.nlapiGetFieldText;
nlapiGetFieldValue = unsafeWindow.nlapiGetFieldValue;
nlapiSearchRecord = unsafeWindow.nlapiSearchRecord;
nlobjSearchFilter = unsafeWindow.nlobjSearchFilter;
nlapiLookupField = unsafeWindow.nlapiLookupField;
nlapiLoadRecord = unsafeWindow.nlapiLoadRecord;
nlapiSubmitRecord = unsafeWindow.nlapiSubmitRecord;
GM_pageTransformations = {};
/**
* The entrypoint for our userscript
*/
function GM_main(jQuery) {
// We want to execute these on every NetSuite page
GM_pageTransformations.header();
GM_pageTransformations.browsertitle();
// Here we build a function name from the path (page being accessed on the NetSuite domain)
var path = location.pathname;
if(path.indexOf('.')>-1) path = path.substr(0,path.indexOf('.'));
path = toCamelCase(path,'/');
// Now we check if a page "GM_pageTransformations" function exists with a matching name
if(GM_pageTransformations[path]) {
console.log('Executing GM_pageTransformations for '+path);
GM_pageTransformations[path]();
} else {
console.log('No GM_pageTransformations for '+path);
}
}
/**
* Changes the header on all pages
*/
GM_pageTransformations['header'] = function() {
// For example, lets make the header background red
GM_addStyle('#ns_header, #ns_header * { background: red !important; }');
}
/**
* Provides useful browser/tab titles for each NetSuite page
*/
GM_pageTransformations['browsertitle'] = function() {
var title = jQuery('.uir-page-title-secondline').text().trim();
var title2 = jQuery('.uir-page-title-firstline').text().trim();
var title3 = jQuery('.ns-dashboard-detail-name').text().trim();
if(title != '') {
document.title = title+(title2 ? ': '+title2 : '')+(title3 ? ': '+title3 : '');
} else if(title2 != '') {
document.title = title2+(title3 ? ': '+title3 : '');
} else if(title3 != '') {
document.title = title3;
}
}
/**
* Changes app center card pages (dashboard pages)
*/
GM_pageTransformations['appCenterCard'] = function() {
// For example, lets make add a new heading text on all Dashboard pages
jQuery('#ns-dashboard-page').prepend('<h1>My New Dashboard Title</h1>');
}
/**
* Convert a given string into camelCase, or CamelCase
* #param {String} string - The input stirng
* #param {String} delimter - The delimiter that seperates the words in the input string (default " ")
* #param {Boolean} capitalizeFirstWord - Wheater or not to capitalize the first word (default false)
*/
function toCamelCase(string, delimiter, capitalizeFirstWord) {
if(!delimiter) delimiter = ' ';
var pieces = string.split(delimiter);
string = '';
for (var i=0; i<pieces.length; i++) {
if(pieces[i].length == 0) continue;
string += pieces[i].charAt(0).toUpperCase() + pieces[i].slice(1);
}
if(!capitalizeFirstWord) string= string.charAt(0).toLowerCase()+string.slice(1);
return string;
}
// ===============
// CREDIT FOR JQUERY INCLUSION CODE: Brock Adams # https://stackoverflow.com/a/12751531/4561907
/**
* Check if we already have a local copy of jQuery, or if we need to fetch it from a 3rd-party server
*/
if (typeof GM_info !== "undefined") {
console.log("Running with local copy of jQuery!");
GM_main(jQuery);
}
else {
console.log ("fetching jQuery from some 3rd-party server.");
add_jQuery(GM_main, "1.9.0");
}
/**
* Add the jQuery into our page for our userscript to use
*/
function add_jQuery(callbackFn, jqVersion) {
var jqVersion = jqVersion || "1.9.0";
var D = document;
var targ = D.getElementsByTagName ('head')[0] || D.body || D.documentElement;
var scriptNode = D.createElement ('script');
scriptNode.src = 'https://ajax.googleapis.com/ajax/libs/jquery/'
+ jqVersion
+ '/jquery.min.js'
;
scriptNode.addEventListener ("load", function () {
var scriptNode = D.createElement ("script");
scriptNode.textContent =
'var gm_jQuery = jQuery.noConflict (true);\n'
+ '(' + callbackFn.toString () + ')(gm_jQuery);'
;
targ.appendChild (scriptNode);
}, false);
targ.appendChild (scriptNode);
}
You can copy and paste that code as-is into a new Userscript and it will do the following:
Make Browser tabs/windows have useful titles (shows order numbers, customer names, vendor names etc - not just "Sales Order")
Change the header background to red (as an example)
Add a new heading to the top of all "Dashboard" pages that says "My New Dashboard Title" (as an example)

What's the "official" way to pass variables in SS2.0?

Run into a kind of silly problem.
I want to pass a variable between stages on a map/reduce script. Is it there an "official" or best way to do this (rather than sending it with the returned results).
This is my last approach:
/**
* #NApiVersion 2.0
* #NScriptType MapReduceScript
*/
define(["N/search", "N/record", "N/email", "N/runtime", "N/task", "/SuiteScripts/Libraries/tools_lib"],
function (search, record, email, runtime, task, tools) {
var ss = runtime.getCurrentSession();
var conf = {};
/**
* Retrieve the CSV contents and return as an object
* #return {Array|*}
*/
function getInputData() {
log.debug("setting", "foo");
ss.set({name: "foo", value: "bar"});
//Session
var foo = ss.get({name: "foo"});
log.debug("foo 1", foo);
//var pass
conf["foo"] = "bar";
return [1, 2, 3];
}
/**
* Search and group by type, all records with matching entries on the CSV field
* #param context
* #return {boolean}
*/
function map(context) {
//Session
var foo = ss.get({name: "foo"});
log.debug("foo 2", foo);
//Var pass
log.debug("foo 3", conf["foo"]);
return false;
}
foo 1 = bar
foo2 = null
foo3 = null
NetSuite stores whatever you return from the previous phase in context.value.
No matter what data type you return, it will always get sent to the next phase as a String, so you need to JSON.parse it if you want to work with a different data type.
function getInputData() {
return [1,2,3];
}
function map(context) {
log.debug(JSON.parse(context.value));
}
You cannot get access to specific variables from previous phases. If you want to pass data along, you need to build an appropriate data structure with the values you want to pass and return it, then parse it out of context.value in the subsequent phase.
This is coming a little late, but one solution/workaround I found is using a Scheduled Script to call a Map/Reduce Script.
You can dynamically set script parameters within the Scheduled Script when you initiate the map/reduce script (using the 'N/task' module).

oauth_signature invalid error while using Magento Rest API with GET filters

I'm having a hard time fetching orders from Magento REST API when I use its GET filters like http://localhost/magento/api/rest/orders/?filter[1][attribute]=entity_id&filter[1][gt]=70&page=1&limit=100
It is giving a "error":[{"code":401,"message":"oauth_problem=signature_invalid"}]
When I try hitting the same API endpoint using a REST Client like Postman, I'm getting back the desired results JSON.
I suspect the square brackets in the filter query might be causing a problem in generating a Oauth signature. All the endpoints without GET filters are working fine.
I'm using the Request node module to make the GET request with the oauth headers.
Is there any fix to avoid the signaturre invalid error?
The problem was within the Request node module I was using to generate a OAuth signature.It didn't factor for square brackets in the URL. I modified the code in the module for including square brackets. Changing the OAuth signature generation method fixed it for me
Since it took me a little while to figure out how to do this I figured I would pass on what I learned. My goal was to make a single request to the Magento’s REST API to return orders with specific order statuses. Looking through the GET filters documentation page wasn’t really helpful until I saw the very last line. Here is the request that I came up with that worked:
http://magentohost.com/api/rest/orders?filter[1][attribute]=status&filter[1][in][1]=pending&filter[1][in][2]=processing
The above request will get you a list of all orders with a status of “pending” or “processing”.
Ref: http://magentoforce.com/2014/08/magento-rest-api-multiple-in-get-filters/
For anyone who is wanting the answer to this here is what worked for me:
const axios = require('axios');
const crypto = require('crypto');
exports.handler = async (event) => {
let base_uri = 'https://YOUR_URL.com'; (ANYTHING UP TO THE /PATH)
const consumer_key = '';
const token_key = '';
const consumer_secret = '';
const token_secret = '';
==> CHANGE THE QUERY PARAMS TO WHATEVER YOU HAVE <==
const queryParameters = {
'searchCriteria[filterGroups][0][filters][0][field]': 'sku',
'searchCriteria[filterGroups][0][filters][0][condition_type]': 'eq',
'searchCriteria[filterGroups][0][filters][0][value]': 'aaaaaaaaa',
};
const timestamp = Math.floor(Date.now() / 1000);
const nonce = await generateNonce();
==> ADD IN YOUR PATH, IT WILL BE APPENDED TO THE BASE URL <==
const path = 'products';
base_uri = `${base_uri}/${path}`;
const reqsign256 = hmacsign256('GET', base_uri, { ...queryParameters, oauth_consumer_key: consumer_key, oauth_nonce: nonce, oauth_signature_method: 'HMAC-SHA256', oauth_timestamp: timestamp, oauth_token: token_key, oauth_version: '1.0' }, consumer_secret, token_secret);
const config = {
method: 'get',
url: base_uri,
headers: {
Authorization: `OAuth oauth_consumer_key="${consumer_key}",oauth_token="${token_key}",oauth_signature_method="HMAC-SHA256",oauth_timestamp="${timestamp}",oauth_nonce="${nonce}",oauth_version="1.0",oauth_signature="${reqsign256}"`
},
params: queryParameters,
};
let response = {};
return await axios(config)
.then(async (response) => {
response = {
statusCode: 200,
body: JSON.stringify(response.data),
};
return response;
})
.catch((error) => {
response = {
statusCode: 200,
body: JSON.stringify(error.message),
};
return response;
});
};
/**
*
* #returns A random string of 11 characters
*/
function generateNonce() {
let nonce = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
for (let i = 0; i < 11; i++) {
nonce += possible.charAt(Math.floor(Math.random() * possible.length));
}
return nonce;
}
/**
*
* #param key
* #param body
* #param algorithm
* #description This is generating the signature using the imported crypto package.
* #returns The generated signature;
*/
function sha(key, body, algorithm) {
return crypto.createHmac(algorithm, key).update(body).digest('base64');
}
/**
*
* #param str
* #description The rfc3986 function takes a string as input and returns its encoded representation as per the rules specified in RFC 3986
* for URI (Uniform Resource Identifier) component encoding. It does this by first using encodeURIComponent to encode the string,
* which replaces certain characters with their percent-encoded representations. It then replaces characters such as !, *, (, ), and '
* with their respective percent-encoded representations, which are specified in RFC 3986 as being reserved characters that must be percent-encoded.
* #returns returns the encoded str value
*/
function rfc3986(str) {
return encodeURIComponent(str).replace(/!/g, '%21').replace(/\*/g, '%2A').replace(/\(/g, '%28').replace(/\)/g, '%29').replace(/'/g, '%27');
}
/**
*
* #param obj
* #description The map function takes an object as input and returns an array of its key-value pairs.
* It does this by iterating through the properties of the object using a for...in loop, and for each property, it checks its value.
* If the value is an array, it pushes an array with the property key and each value of the array into the result array.
* If the value is an object, it pushes an array with a string representation of the property key concatenated with the property name in square brackets
* and the property value into the result array.
* If the value is neither an array nor an object, it pushes an array with the property key and value into the result array.
* Finally, it returns the result array.
* #returns arr
*/
function map(obj) {
let key,
val,
arr = [];
for (key in obj) {
val = obj[key];
if (Array.isArray(val)) for (let i = 0; i < val.length; i++) arr.push([key, val[i]]);
else if (typeof val === 'object') for (let prop in val) arr.push([key + '[' + prop + ']', val[prop]]);
else arr.push([key, val]);
}
return arr;
}
/**
*
* #param a
* #param b
* #description Used to sort the oauth paramters into ascending order -- this is required for oauth1 to work.
* #returns the comparison result
*/
function compare(a, b) {
return a > b ? 1 : a < b ? -1 : 0;
}
/**
*
* #param httpMethod
* #param base_uri
* #param params
* #description
* 1. First, the name and value of each parameter are encoded
* 2. The parameters are sorted by name, using ascending byte value ordering. If two or more parameters share the same name, they are sorted by their value.
* 3. The name of each parameter is concatenated to its corresponding value using an "=" character (ASCII code 61) as a separator, even if the value is empty.
* for example in this case it is assigning oauth_nonce = 'foo' or oauth_signature_method = 'HMAC-SHA256' etc
* 4. The sorted name/value pairs are concatenated together into single string by using an "&" character (ASCII code 38) as separator.
* The final output will be something like this:
* GET&https%3A%2F%2Fstaging2.ospreylondon.com%2Frest%2FV1%2Fproducts&oauth_consumer_key%3Dxxx%26oauth_nonce%3xxxoauth_signature_method%3DHMAC-SHA256%26oauth_timestamp%3Dxxx%26oauth_token%3xxx%26oauth_version%3D1.0%26 ==> There will also be any url paramaters added at the end.
* #returns base url
*/
function generateBase(httpMethod, base_uri, params) {
let normalized = map(params)
// step 1
.map(function (p) {
return [rfc3986(p[0]), rfc3986(p[1] || '')];
})
// step 2
.sort(function (a, b) {
return compare(a[0], b[0]) || compare(a[1], b[1]);
})
//step 3
.map(function (p) {
return p.join('=');
})
//step 4
.join('&');
let base = [rfc3986(httpMethod ? httpMethod.toUpperCase() : 'GET'), rfc3986(base_uri), rfc3986(normalized)].join('&');
return base;
}
/**
*
* #param httpMethod
* #param base_uri
* #param params
* #param consumer_secret
* #param token_secret
* #description This takes the paramaters passed in, creates a base uri, creates a key (consumer secret and token secret with & between them)
* #returns this then returns the result of the sha method ==> this is the signature used in the request.
*/
function hmacsign256(httpMethod, base_uri, params, consumer_secret, token_secret) {
let base = generateBase(httpMethod, base_uri, params);
let key = [consumer_secret || '', token_secret || ''].map(rfc3986).join('&');
return sha(key, base, 'sha256');
}
Here is the detailed answer...
http://magentoforce.com/2014/08/magento-rest-api-multiple-in-get-filters/

Resources