I am trying to create an Adobe Indesign JavaScript that will copy the link names of the selected items to the system clipboard. Example if I had 3 frames selected with 3 different images placed into them on the clipboard I would get
Image01.jpg
Image02.png
Image03.pdf
try {
// Get the selected items
var selectedItems = app.activeDocument.selection;
// Initialize an empty array to store the file names
var fileNames = [];
// Check if there are any selected items
if (selectedItems.length > 0) {
// Loop through the selected items
for (var i = 0; i < selectedItems.length; i++) {
// Check if the selected item is an Link
if (selectedItems[i] instanceof Link) {
// If it's an image, add the file name to the array
fileNames.push(selectedItems[i].itemLink.name);
}
}
// Check if the file names array is empty
if (fileNames.length > 0) {
// Join the file names into a single string
var fileNamesString = fileNames.join("\n");
// Copy the string to the clipboard
app.copy(fileNamesString);
}
else {
// If the file names array is empty, display an alert message
alert("None of the selected items are images.");
}
}
else {
// If there are no selected items, display an alert message
alert("Please select at least one item.");
}
}
catch (e) {
// If an exception is thrown, display an alert message
alert("An error occurred:\n" + e);
}
So far I have tried running the code above but it is not working and throws an error both if I have a frame selected with a link in it and if I have nothing selected.
An error occurred:
TypeError: undefined is not an object
Help getting this to work much appreciated.
Try to change the line:
if (selectedItems[i] instanceof Link) {
// If it's an image, add the file name to the array
fileNames.push(selectedItems[i].itemLink.name);
}
with
try { fileNames.push(selectedItems[i].graphics[0].itemLink.name) }
catch(e) { fileNames.push(selectedItems[i].itemLink.name) }
And this obvious approach app.copy(fileNamesString); doesn't work in InDesign. Unfortunately.
If you want to put text into the clipboard you have to create a text frame, put the text into the frame, select the text and then use app.copy() method.
Here is the dedicated function:
function copy_to_clipboard(text) {
var frame = app.activeDocument.pages[0].textFrames.add();
frame.geometricBounds = [0,0,200,100];
frame.contents = text;
frame.parentStory.texts.everyItem().select();
app.copy();
$.sleep(300); // just in case
frame.remove();
}
And now you have replace the line:
app.copy(fileNamesString);
with
copy_to_clipboard(fileNamesString);
Related
I am working on Microsoft Excel Add-in in react, I want to assign colors to the specific cells based on the value in it i.e, by color code (See Board Color column in image below). So what I did is, get column of the table, and iterate through that column cells and load "value" for each cell. Then iterate on array of those values and try to fill a respective color to each cell.
on iteration, Values are printed in console correctly, but color are not being filled, now it has become headache for me. It is only happening if I assign color in/after "await this.context.sync()". Before that, Color are filled (checked by dummy cells).
Below is the code:
// ExcelAPI.js class
async setBoardColor() {
console.log("setBoardColor()");
try {
let workItemIDColumn = this.workitemTable.columns.getItem("BOARD COLOR").getDataBodyRange().load("values"); // .load(["values", "address"]);
// let workItemIDColumn = await range.load(["values", "address"]);
console.log("Workitem IDs 00 : ", workItemIDColumn);
// console.log("Workitem IDs count : ", workItemIDColumn.values.length);
await this.context.sync().then(() => {
let accountHeaderRange = this.currentWorksheet.getRange("A2:B2");
accountHeaderRange.format.fill.color = "red";
for (var row = 0; row < workItemIDColumn.values.length; row++) {
console.log("in loop : ", row);
console.log("values : " + workItemIDColumn.values[row][0]);
// workItemIDColumn.getRow(row).getRange("A2:B2").format.fill.color = "red";
if (workItemIDColumn.values[row][0] != "") {
console.log("I am in");
workItemIDColumn.getRow(row).getResizedRange(0, -2).format.fill.color = "red"; // workItemIDColumn.values[row][0];
// workItemIDColumn.getRow(row).format.protection.locked = false;
}
}
});
// console.log("Workitem IDs check : ");
} catch (error) {
console.log("setBoardColor() ", error);
}
}
Calling of above method is in main class component.
renderExcelContent = async (workItems) => {
try {
await Excel.run(async (context) => {
const currentWorksheet = context.workbook.worksheets.getActiveWorksheet();
let excelAPI = new ExcelAPI(context, currentWorksheet);
excelAPI.setBoardColor();
currentWorksheet.getRange("A2:B2").format.protection.locked = false;
currentWorksheet.protection.protect();
// eslint-disable-next-line no-undef
if (Office.context.requirements.isSetSupported("ExcelApi", "1.2")) {
currentWorksheet.getUsedRange().format.autofitColumns();
currentWorksheet.getUsedRange().format.autofitRows();
}
currentWorksheet.activate();
context.runtime.enableEvents = true;
});
} catch (error) {
console.error(error);
}
};
That's just how the Excel JavaScript API works. The things you're doing before calling sync are just interacting with JavaScript objects that aren't actually in the spreadsheet. It's the sync call that writes the changes you've made to the actual worksheet. So if you want to see changes, you have to use sync.
From the documentation (my emphasis):
Excel.RequestContext class
The RequestContext object facilitates requests to the Excel application. Since the Office add-in and the Excel application run in two different processes, the request context is required to get access to the Excel object model from the add-in.
and from sync:
Synchronizes the state between JavaScript proxy objects and the Office document, by executing instructions queued on the request context and retrieving properties of loaded Office objects for use in your code.
(Note that an implicit sync happens after your Excel.run callback returns.)
I have fixed this by "await" before calling setBoardColor() in main class.
await excelAPI.setBoardColor();
How can I set the badge number on a specific tab only? So far I have a code that sets the badge number on all the tabs.. I've been reading around A LOT, but there doesn't seem to be a whole lot of information about this, so perhaps I will find a solution for this here.
I would like something like Adblock Plus, which sets the badge number for a specific tab. This is pretty easy in Chrome etc, but doesn't seem to be the case in Safari.
Does anyone know how extensions like Adblock plus shows the badge number on a specific tab?
So far I only have this code, but as mentioned, it sets the badge on all the tabs, which is not the result I want.
safari.extension.toolbarItems[0].badge = 2;
Edit:
I have been looking at the source code of Adblock plus, and a few other extensions that had this function. And it seems it is using some prototype.
Adblock plus background snippet:
BrowserAction.prototype = {
_set: function(name, value)
{
var toolbarItem = getToolbarItemForWindow(this._page._tab.browserWindow);
if (!toolbarItem)
{
return;
}
var property = toolbarItemProperties[name];
if (!property)
{
property = toolbarItemProperties[name] = {
pages: new ext.PageMap(),
global: toolbarItem[name]
};
}
property.pages.set(this._page, value);
if (isPageActive(this._page))
{
toolbarItem[name] = value;
}
},
setIcon: function(path)
{
this._set("image", safari.extension.baseURI + path.replace("$size", "16"));
},
setBadge: function(badge)
{
if (!badge)
{
this._set("badge", 0);
}
else if ("number" in badge)
{
this._set("badge", badge.number);
}
}
};
Content script (adblockplus.js)
FilterNotifier.on("filter.hitCount", function(filter, newValue, oldValue, page)
{
if (!(filter instanceof BlockingFilter) || !page)
{
return;
}
Prefs.blocked_total++;
var blocked = blockedPerPage.get(page) || 0;
blockedPerPage.set(page, ++blocked);
if (Prefs.show_statsinicon)
{
page.browserAction.setBadge(
{
color: badgeColor,
number: blocked
});
}
});
It seems this is how Adblock plus does it, but so far I haven't been able to replicate it. Still trying though..
Okay, so I finally found a solution for this, and thought I would share what I did, in case somebody else is in the same situation.
This morning I got the idea of storing the data in an array, when the user visits one of the websites I want to display the badge number on (doesn't store all websites the user visits), only if it matched one of the websites I wanted to target. I stored the following data in the array: root domain (example.com) and the badgeNumber.
For this to work, you need to make an array of the root domain of the websites you want to target, and then only execute the following when it matches, otherwise the array would fill up very quickly, and we don't want too much data in it.
In the global page, start by making an empty array to store the data
var badgeUpdateArray = [];
You then need to set up message handling in your global page as well.
safari.application.addEventListener('message', handleMessage, false);
function handleMessage(event) {
if(event.name === "setBadgeText"){
var id = badgeUpdateArray.length + 1;
var isFound = 0;
var found = badgeUpdateArray.some(function (el) {
if(el.identifier === event.message.identifier){
// Was found
isFound = 1;
}
});
if (isFound == 0) {
// Not found, add to the array
badgeUpdateArray.push({identifier:event.message.identifier,badgeNumber:event.message.badgeNumber});
}
// Set the badge number
safari.extension.toolbarItems[0].badge = event.message.badgeNumber;
}
}
Now we need to send the message from the content script to the global page. You need to get the root domain (example.com), I'm not getting into that here, as it's pretty easy. You will also need the badgeNumber value, this can be gathered from wherever (GET request, or elsewhere..)
Remember, only execute this code if the website matches your target domains.
var message = {
identifier: domain,
badgeNumber: rows.length
}
safari.self.tab.dispatchMessage("setBadgeText", message);
This will send the message, and store the data in the array, it will also set the badge number.
Now, for this to be working on different tabs, you will need to make an event handler for "activate" on the global page, this will run whenever a tab is active.
safari.application.addEventListener("activate", updateBadge, true);
function updateBadge(){
var cDomain = safari.application.activeBrowserWindow.activeTab.url;
cDomain = cDomain.replace("www3.","");
cDomain = cDomain.replace("www2.","");
cDomain = cDomain.replace("www1.","");
cDomain = cDomain.replace("www.","");
cDomain = new URL(cDomain);
cDomain = cDomain.hostname;
var id = badgeUpdateArray.length + 1;
var isFound = 0;
var badgeNumber = 0;
var found = badgeUpdateArray.some(function (el) {
badgeNumber = el.badgeNumber;
if(el.identifier === cDomain){
// Was found, set the badge number
isFound = 1;
safari.extension.toolbarItems[0].badge = el.badgeNumber;
}
});
if (isFound == 0) {
// Was not found
safari.extension.toolbarItems[0].badge = 0;
}
}
Hopefully I've got it all in here, and at least something that works, though I have to say that I would prefer an easier way of storing it.. like Chrome etc does it, with the tab API.
I am working on a web application that allows for divs to be drag and dropped/rearranged. The new order of the divs is stored in local storage and if the page is refreshed or navigated away from and then back to, the user's chosen order is then fetched from local storage and displayed to the page. So far I have most of these things working, however when the page is refreshed/navigated away from and back to, the divs are no longer able to be moved around. In the console it appears that the event listeners are no longer on the draggable targets. How can I structure/edit my code so that the divs can be rearranged even if the user has refreshed or come back to the page at a later point in time? (I think the way I am handling events currently is also not very good, so suggestions for how to improve that are more than welcome.)
// get two groups of elements, those that are draggable and those that are drop targets
var draggable = document.querySelectorAll('[draggable]');
var targets = document.querySelectorAll('[data-drop-target]');
// div immediately surrounding bus routes
var busList = document.getElementById("bus-list");
var busListBuses = busList.children;
// make array from busListBuses which is an htmlCollection (not currently using this)
var divArr = Array.from(busListBuses);
// store the id of the draggable element when drag starts
function handleDragStart(e) {
e.dataTransfer.setData("text", this.id); // sets 'text' value to equal the id of this
this.classList.add("drag-start"); // class for styling the element being dragged
}
function handleDragEnd(e) {
e.target.classList.remove('drag-start');
}
function handleDragEnterLeave(e) {
//
}
// handles dragover event (moving your source div over the target div element)
// If drop event occurs, the function retrieves the draggable element’s id from the DataTransfer object
function handleOverDrop(e) {
e.preventDefault();
var draggedId = e.dataTransfer.getData("text"); // retrieves drag data (DOMString) for specified type
var draggedEl = document.getElementById(draggedId);
draggedEl.parentNode.insertBefore(draggedEl, this); // inserts element being dragged into list
var draggedArray = Array.from(draggedEl.parentNode.children); // creates new array which updates current location of each route
if (e.type === "drop") {
// when dropped, update localstorage
savePage(draggedArray);
}
}
// get each full bus-route div in #bus-list with p content as single arr item each
// called when item is dropped
function savePage(dArray) {
// honestly i can't remember what this does precisely
// but i can't seem to add to localstorage in the way i want without it
var arr = Array.prototype.map.call(dArray, function(elem) {
return elem.outerHTML;
});
localStorage.newList = JSON.stringify(arr); // have to stringify the array in order to add it to localstorage
}
// ideally it should just update the order of the bus routes to reflect local storage
// and add classes/ids to the divs etc. (hence using outerHTML)
function restorePage() {
// getting the item from localstorage
var getData = localStorage.getItem("newList");
// parsing it back into an array
var parsedData = JSON.parse(getData);
// string to hold contents of array so they can be display via innerHTML
var fullList = "";
if (localStorage.getItem("newList") === null) {
return; // maybe this is wrong but if there's nothing in local storage, don't do anything
} else {
for (let i = 0; i < parsedData.length; i++) {
fullList = fullList + parsedData[i];
}
busList.innerHTML = fullList;
}
}
// submit button to save changes to db
// probably better way to do this
for (let i = 0; i < draggable.length; i++) {
draggable[i].addEventListener("dragstart", handleDragStart);
draggable[i].addEventListener("dragend", handleDragEnd);
}
// drop target elements
for (let i = 0; i < targets.length; i++) {
targets[i].addEventListener("dragover", handleOverDrop);
targets[i].addEventListener("drop", handleOverDrop);
targets[i].addEventListener("dragenter", handleDragEnterLeave);
targets[i].addEventListener("dragleave", handleDragEnterLeave);
}
window.addEventListener('load', function(){
restorePage();
});
I am using React with Pug, here is the Pug file in case that's helpful.
html
head
title
link(rel='stylesheet', type='text/css', href='stylesheets/normalize.css' )
link(rel='stylesheet', type='text/css', href='stylesheets/style.css' )
body
div.container
div#bus-list
for route in routeInfo
- var routeID = route.ID
- var routePos = route.position
div(class=route.type, draggable="true", data-drop-target="true", id=`r-${routePos}`).bus-route
p #{route.route} #{route.route_name}
button(type="submit", value="submit") done!
script(src='js/styling.js')
I think problem is in the sequence of your code.
JavaScript code is bening parsed as script. So browser executes commands during script loading.
How it works:
Browser loads DOM without any items
Browser loads JS and adds listeners to the elements (which aren't loaded yet)
Brower fires onLoad() and your script loads elements from the storage.
End of the cycle.
You need to add listeners after you load the elements. Not before that.
Off topic
You do some repetitive calls.
You do
var getData = localStorage.getItem("newList");
var parsedData = JSON.parse(getData);
var fullList = "";
if (localStorage.getItem("newList") === null) {
return;
}
Instead of
var getData = localStorage.getItem("newList");
if (getData === null) {
return;
}
var parsedData = JSON.parse(getData);
var fullList = "";
It hurts readability and perfomance.
I would suggest you to read some books about C languague and Code Style. That will help you on your journey.
I'm having some trouble with my script. Basically, that works getting data from one course and inputting the values to one Sheet. That's working perfectly.
But when one of my students input 'enter' command at that course, I have trouble to read it in Excel. SO, I have to find and replace the enter at Google Spreadsheet and change it for "; ".
Works perfectly doing it manually, but I can't do it by script.
Here the piece:
// 1. Enter sheet name where data is to be written below
var SHEET_NAME = "DATA";
// 2. Run > setup
//
// 3. Publish > Deploy as web app
// - enter Project Version name and click 'Save New Version'
// - set security level and enable service (most likely execute as 'me' and access 'anyone, even anonymously)
//
// 4. Copy the 'Current web app URL' and post this in your form/script action
//
// 5. Insert column names on your destination sheet matching the parameter names of the data you are passing in (exactly matching case)
var SCRIPT_PROP = PropertiesService.getScriptProperties(); // new property service
// If you don't want to expose either GET or POST methods you can comment out the appropriate function
function doGet(e){
return handleResponse(e);
}
function doPost(e){
return handleResponse(e);
}
function handleResponse(e) {
// shortly after my original solution Google announced the LockService[1]
// this prevents concurrent access overwritting data
// [1] http://googleappsdeveloper.blogspot.co.uk/2011/10/concurrency-and-google-apps-script.html
// we want a public lock, one that locks for all invocations
var lock = LockService.getPublicLock();
lock.waitLock(30000); // wait 30 seconds before conceding defeat.
try {
// next set where we write the data - you could write to multiple/alternate destinations
var doc = SpreadsheetApp.openById(SCRIPT_PROP.getProperty("CHANGED BY SECURITY REASON"));
var sheet = doc.getSheetByName(SHEET_NAME);
// we'll assume header is in row 1 but you can override with header_row in GET/POST data
var headRow = e.parameter.header_row || 1;
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var nextRow = sheet.getLastRow()+1; // get next row
var row = [];
// loop through the header columns
for (i in headers){
if (headers[i] == "Timestamp"){ // special case if you include a 'Timestamp' column
row.push(new Date());
} else { // else use header name to get data
row.push(e.parameter[headers[i]]);
}
}
// more efficient to set values as [][] array than individually
sheet.getRange(nextRow, 1, 1, row.length).setValues([row]);
// return json success results
return ContentService
.createTextOutput(JSON.stringify({"result":"success", "row": nextRow}))
.setMimeType(ContentService.MimeType.JSON);
} catch(e){
// if error return this
return ContentService
.createTextOutput(JSON.stringify({"result":"error", "error": e}))
.setMimeType(ContentService.MimeType.JSON);
} finally { //release lock
lock.releaseLock();
}
}
function setup() {
var doc = SpreadsheetApp.getActiveSpreadsheet();
SCRIPT_PROP.setProperty("1Un5A61M8CJDBGDAB-Tx-lYgKYaVB2RSfn9QAQ5Q-sZs", doc.getId());
}
Where can I input:
doc.replaceText("\r\n|\n|\r",";[[:space:]]");
to work straight after the spreadsheet received the data? I can't open the sheet after the course is done to do it manually or even play the script.
(Sorry about any language mistake)
Thanks so much!!!!!!!!!!!!!!
Based from this related post, you can achieve this by reading in all values in the sheet (as an Array), looping over the array, replacing the values, then writing the entire array back to the sheet.
You may want to check the sample code in this thread.
Try this:
function fandr() {
var r=SpreadsheetApp.getActiveSheet().getDataRange();
var rws=r.getNumRows();
var cls=r.getNumColumns();
var i,j,a,find,repl;
find="abc";
repl="xyz";
for (i=1;i<=rws;i++) {
for (j=1;j<=cls;j++) {
a=r.getCell(i, j).getValue();
if (r.getCell(i,j).getFormula()) {continue;}
try {
a=a.replace(find,repl);
r.getCell(i, j).setValue(a);
}
catch (err) {continue;}
}
}
}
This time it will replace find in a string. I can put it back to only
replace if the string is find if that's better. Basically, I replace:
try {
a=a.replace(find,repl);
r.getCell(i, j).setValue(a);
}
catch (err) {continue;}
with
if (a==find) { r.getCell(i, j).setValue(repl);}
I have a tile list in a HTML Lightswitch Client (current version) and want to enable functionality for users to be able to click an element and display the addEdit screen for that element.
The addEdit screen uses a query Complaints and the tile list uses a query vw_Upcoming_Complaints. Both queries have a common unique attribute Complant_ID.
I currently have the following code:
myapp.Main.vw_Upcoming_Complaints_Selected_execute = function (screen) {
myapp.showAddEditComplaints(null, {
beforeShown: function (addEditComplaintScreen) {
addEditComplaintScreen.Complaint = screen.Upcoming_Complaints.selectedItem;
},
afterClosed: function (addEditScreen, navigationAction) {
screen.selected_Complaint.details.refresh();
}
});
};
Lightswitch currently shows the correct Complaint_ID on the addEdit screen but does not fetch the remainder of the attributes.
How can I tell lightswitch that the common identifier is complaint_Id and it should find the rest of the attributes in the Complaints data set?
There is no way for me to edit the original Complaints query to contain all the attributes of the vw_Upcoming_Work dataset.
One option would be to update your vw_Upcoming_Complaints_Selected_execute function along the following lines:
myapp.Main.vw_Upcoming_Complaints_Selected_execute = function (screen) {
myapp.showAddEditComplaint(null, {
beforeShown: function (addEditComplaintScreen) {
var id = screen.Upcoming_Complaints.selectedItem.Id;
myapp.activeDataWorkspace.ApplicationData.Complaints_SingleOrDefault(id).execute().then(function onComplete(result) {
if (result && result.results && result.results.length !== 0) {
addEditComplaintScreen.Complaint = result.results[0];
}
});
},
afterClosed: function (addEditScreen, navigationAction) {
screen.selected_Complaint.details.refresh();
}
});
};
This update assumes that your DataSource is called ApplicationData, and the complaint id is the key of your Complaints query.
Based upon these assumptions, the revised version simply locates the matching Complaint entity, by using the _SingleOrDefault method against your Complaints query, and assigns it to the add edit screen.