Error With Email Change Notifications Using Project Tracker Template - google-app-maker

Short Version:
I am using the code from the Project Tracker Template to send out emails showing the change in a status for a field (Contact Name changed from: Billy -> Susan).
Everything works perfectly expect when I have a field that is a Date instead of a String. If I have a Date field in the code I get the following error:
'string' value is expected for 'NewValue' field in model 'SystemOrdersHistory', but found object. Error: 'string' value is expected for 'NewValue' field in model 'SystemOrdersHistory', but found object. at onSystemOrdersSave_ (Datasources:218) at models.SystemOrders.onSaveEvent:1
Modifying records: (Error) : 'string' value is expected for 'NewValue' field in model 'SystemOrdersHistory', but found object.
(Error) : 'string' value is expected for 'NewValue' field in model 'SystemOrdersHistory', but found object.
Any help would be greatly appreciated!
Long Version
I am using the code below (adjusted to fit the names of my models and fields).
Whenever I add a Date field (ex: DeliveryDate) to the function "notifyAboutItemChanges_" function and the "onSystemOrdersSave_" function I get the error about "expecting a string, but found an object".
Note: The OldValue and NewValue fields in the "History" model are both Strings.
Notifications Server Script:
/**
* Sends email.
* #param {!string} to - email address of a recipient.
* #param {!string} subject - subject of email message.
* #param {!string} body - body of email message.
*/
function sendEmail_(to, subject, body) {
try {
MailApp.sendEmail({
to: to,
subject: subject,
htmlBody: body,
noReply: true
});
} catch (e) {
// Suppressing errors in email sending because email notifications
// are not critical for the functioning of the app.
console.error(JSON.stringify(e));
}
}
/**
* Sends email notification about recent project item changes to item owner
* and assignee.
* #param {!Array<ItemHistory>} changes - list of recent project item changes.
*/
function notifyAboutItemChanges_(changes) {
if (!changes || changes.length < 2) {
return;
}
var settings = getAppSettingsRecord_()[0];
if (!settings.EnableEmailNotifications) {
return;
}
var data = {
appUrl: settings.AppUrl,
itemShowName: changes[0].ShowName,
itemUsersPosition: changes[0].UsersPosition,
itemDeliveryInfo: changes[0].DeliveryInfo,
itemDeliveryDate: changes[0].DeliveryDate,
itemKey: changes[0]._key,
itemName: changes[0].Name,
modifiedBy: changes[0].ModifiedBy,
changes: changes
};
// Email subject.
var subjectTemplate =
HtmlService.createTemplate(settings.NotificationEmailSubject);
subjectTemplate.data = data;
var subject = subjectTemplate.evaluate().getContent();
// Email body.
var emailTemplate =
HtmlService.createTemplate(settings.NotificationEmailBody);
emailTemplate.data = data;
var htmlBody = emailTemplate.evaluate().getContent();
sendEmail_('user#gmail.com', subject, htmlBody);
Datasources Server Script:
/**
* Item key URL parameter.
*/
var ITEM_KEY = 'itemKey';
/**
* Checks that Application Settings record already exists.
* Otherwise creates a new one.
* #return {!Array<AppSettings>} app settings record as an array.
*/
function getAppSettingsRecord_() {
var newQuery = app.models.AppSettings.newQuery();
var settingsRecords = newQuery.run();
if (settingsRecords.length > 1) {
console.warn('There is more than one(%s) App Settings entries' +
'in the database', settingsRecords.length);
}
if (settingsRecords.length === 0) {
var settingsRecord = app.models.AppSettings.newRecord();
settingsRecord.AppUrl = ScriptApp.getService().getUrl();
settingsRecord.NotificationEmailSubject =
'A change has been made to <?= data.itemShowName?>: <?= data.itemUsersPosition?>';
settingsRecord.NotificationEmailBody =
'Hello!\n<br/>\n<p><b><?= data.modifiedBy ?></b> ' +
'made the following changes: </p>\n' +
'<? for (var i = 1; i < data.changes.length; i++) {\n' +
'\tvar change = data.changes[i]; ?>\n' +
'\t<b><?= change.FieldName ?>: </b>\n' +
'\t<? if (change.FieldName === "Comment") { ?>\n' +
'\t\t<div style="white-space: pre-line;"><?= change.NewValue ?></div>' +
'\n\t<? } else { ?>\n ' +
'\t\t<?= change.OldValue ?> → <?= change.NewValue ?>' +
'\n\t<? } ?>\n\t<br/>\n' +
'<? } ?>\n<br/>\n' +
'<a href="<?= data.appUrl ?>?' + ITEM_KEY + '=<?= data.itemKey ?>' +
'#EditItem" target="_blank">Go to the project item</a>';
app.saveRecords([settingsRecord]);
return [settingsRecord];
} else {
return settingsRecords;
}
}
/**
* Populates project record with required data on project create event.
* #param {!Project} project - project being created.
*/
function onProjectCreate_(project) {
var date = new Date();
project.CreatedDate = date;
project.ModifiedDate = date;
project.ModifiedBy = currentUserEmail_();
}
/**
* Audits project on changes.
* #param {!Project} project - project being modified.
*/
function onProjectSave_(project) {
project.ModifiedDate = new Date();
project.ModifiedBy = currentUserEmail_();
}
/**
* Populates project item with required data on item create event, adds
* comment entry to the project item history.
* #param {!SystemOrders} SystemOrders - project item being created.
*/
function onSystemOrdersCreate_(SystemOrders) {
var date = new Date();
var editor = currentUserEmail_();
if (SystemOrders.Comment) {
SystemOrders.Comment = SystemOrders.Comment.trim();
}
SystemOrders.CreatedDate = date;
SystemOrders.Owner = editor;
SystemOrders.ModifiedDate = date;
SystemOrders.ModifiedBy = editor;
if (SystemOrders.Comment) {
var history = app.models.SystemOrdersHistory.newRecord();
history.CreatedBy = currentUserEmail_();
history.CreatedDate = new Date();
history.FieldName = 'Comment';
history.NewValue = SystemOrders.Comment;
app.saveRecords([history]);
SystemOrders.History.push(history);
}
}
/**
* Calculates history entries sum for {Array<SystemOrders>}.
* #param {!number} historySum - the accumulated number of history entries
* previously returned in the last invocation of the callback, or
* initialValue, if supplied.
* #param {!SystemOrders} SystemOrders - the current {SystemOrders} being
* processed in the array.
* #return {!number} history entries sum.
*/
function sumHistory_(historySum, SystemOrders) {
return historySum + SystemOrders.History.length;
}
/**
* Calculates potential project deletion impact.
* Throws an error if there is no project with the key provided.
* #param {!string} projectKey - project key to calculate deletion impact.
*/
function getDeleteProjectImpact(projectKey) {
var projectQuery = app.models.Project.newQuery();
projectQuery.prefetch.Items._add();
projectQuery.prefetch.Items.History._add();
projectQuery.filters._key._equals = projectKey;
var projects = projectQuery.run();
if (projects.length === 0) {
throw new Error('Project with key ' + projectKey + ' was not found.');
}
var SystemOrderss = projects[0].Items;
return {
affectedItems: SystemOrderss.length,
affectedHistory: SystemOrderss.reduce(sumHistory_, 0)
};
}
/**
* Checks that project item readonly fields were not modified.
* Throws an error if user attempts to modify read only fields.
* #param {!SystemOrders} record - modified project item.
* #param {!SystemOrders} oldRecord - project item before modification.
*/
function validateItemChange_(record, oldRecord) {
var readonlyFields = [
'CreatedDate',
'ModifiedBy',
'ModifiedDate',
'Owner'
];
for (var i = 0; i < readonlyFields.length; i++) {
var field = readonlyFields[i];
var newValue = record[field];
var oldValue = oldRecord[field];
var isDate = newValue instanceof Date && oldValue instanceof Date;
if (isDate === true) {
newValue = record[field].getDate();
oldValue = oldRecord[field].getDate();
}
if (newValue === oldValue) {
continue;
}
throw new Error(field + ' field is read only');
}
}
/**
* Handles project item change event, creates history entries for each changed
* field.
* #param {!SystemOrders} record - modified project item.
* #param {!SystemOrders} oldRecord - project item before modification.
*/
function onSystemOrdersSave_(record, oldRecord) {
validateItemChange_(record, oldRecord);
var editableFields = [
'ShowName',
'UsersPosition',
'DeliveryInfo',
'DeliveryDate'
];
var editor = currentUserEmail_();
var date = new Date();
var changes = [record];
record.ModifiedBy = editor;
record.ModifiedDate = date;
for (var i = 0; i < editableFields.length; i++) {
var field = editableFields[i];
var newValue = record[field];
var oldValue = oldRecord[field];
if (newValue !== oldValue) {
var history = app.models.SystemOrdersHistory.newRecord();
history.Item = record;
history.CreatedBy = editor;
history.CreatedDate = date;
history.FieldName = field;
history.NewValue = newValue;
history.OldValue = oldValue;
changes.push(history);
}
}
app.saveRecords(changes);
notifyAboutItemChanges_(changes);
}
/**
* Counts project items by some grouping criteria(field).
* #param {!string} projectKey - project key to calculate stats.
* #param {!string} grouping - project item field to group items by.
* #param {!Array<string>} groupingValues - possible field values.
* #return {!Array<SystemOrderssBreakdown>} grouped project items counts.
*/
function getSystemOrderssBreakdown_(projectKey, grouping, groupingValues) {
if (!grouping || !groupingValues || groupingValues.length === 0) {
return [];
}
var itemsQuery = app.models.SystemOrders.newQuery();
itemsQuery.prefetch.Project._add();
itemsQuery.filters.Project._key._equals = projectKey;
var items = itemsQuery.run();
if (items.length === 0) {
return [];
}
var records = [];
var map = {};
for (var i = 0; i < items.length; i++) {
var itemGrouping = items[i][grouping];
if (!map[itemGrouping]) {
map[itemGrouping] = 0;
}
map[itemGrouping]++;
}
for (i = 0; i < groupingValues.length; i++) {
var breakdownRecord = app.models.SystemOrderssBreakdown.newRecord();
var groupingValue = groupingValues[i];
breakdownRecord.Grouping = groupingValue;
breakdownRecord.ItemsCount = map[groupingValue] || 0;
records.push(breakdownRecord);
}
return records;
}

It fails here:
// history.NewValue and history.OldValue are strings
// newValue and oldValue can be of any type (Boolean, Number, Date,
// but not a relation as of now)
// You are getting an exception because you are not casting types
history.NewValue = newValue;
history.OldValue = oldValue;
You can fix it by adding fields of each possible type to your history model (NewStringValue, NewDateValue, NewBoolValue, NewNumberValue, OldStringValue...). With that approach you'll get benefits of strong typing, but your code and UI will become significantly more complex...
You can store all your fields' history as strings(like you are doing now), but in this case you'll need to think about formatting and localization in advance:
function fieldToString(field, fieldValue) {
// TODO: pass field metadata to individually handle
// different data types.
return fieldValue !== null ? fieldValue.toString() : null;
}
...
history.NewValue = fieldToString(field, newValue);
history.OldValue = fieldToString(field, oldValue);
...

Related

Is there way to get attachments details before starting the workflow?

Before I start the business process, I select the attachments. I can do it many times, remove attachments and choose again.
I want to display dynamic table with information about attachments.
For example, to retrieve all the attachments details, I use such code:
...
var divWithAnchors = YAHOO.util.Selector.query("#page_x002e_data-form_x002e_task-details_x0023_default_assoc_packageItems-cntrl")[0];
var anchors = divWithAnchors.getElementsByTagName('a');
var attachments = new Array();
for(var i = 0; i < anchors.length; i++) {
attachments[i] = anchors[i].href.split('=')[1];
}
...
It gives me references to nodes, for example:
...
workspace://SpacesStore/c5a27463-c2aa-4c70-aca7-1f999d3ac76a
workspace://SpacesStore/29e9f035-403c-47b6-8421-624d584ff7eb
workspace://SpacesStore/712aaca2-9c90-4733-a690-bbf9bacb26e6
workspace://SpacesStore/68893fde-ee7c-4ecb-a2df-d4953dc69439
...
Then I can do AJAX requests to the REST back-end (WebScripts) and get the responses:
...
for(var i = 0; i < attachments.length; i++) {
Alfresco.util.Ajax.jsonGet(
...
// parse JSON and fill the table
Is this the correct way? I'm not sure about the ID:
page_x002e_data-form_x002e_task-details_x0023_default_assoc_packageItems-cntrl
Is this a constant?.. Can this identifier be changed?
In fact, all these NodeRefs are available in the object selectedItems = {} and can be obtained in the method getAddedItems() (see object-finder.js):
...
/**
* Selected items. Keeps a list of selected items for correct Add button state.
*
* #property selectedItems
* #type object
*/
selectedItems: null,
...
/**
* Returns items that have been added to the current value
*
* #method getAddedItems
* #return {array}
*/
getAddedItems: function ObjectFinder_getAddedItems() {
var addedItems = [],
currentItems = Alfresco.util.arrayToObject(this.options.currentValue.split(","));
for (var item in this.selectedItems) {
if (this.selectedItems.hasOwnProperty(item)) {
if (!(item in currentItems)) {
addedItems.push(item);
}
}
}
return addedItems;
},
...
Next, is needed to send these NodeRefs to the WebScript and get all the necessary properties by using NodeService service.

Alfresco media viewer web-preview extensibility

So I followed Will Abson's guide and source code for extending custom media viewers in Alfresco.
I have a couple of issues though.
I'm already using 4.2+ Alfresco, so no need to use head.ftl as deprecated, I'm using a second extensibility module to add my own configuration automatically, BUT:
how can I access the jsNode in my web-preview.get.js? Or better, is there a way to access properties values and aspects of a node which is being displayed?
I know about both server and client side var jsNode = new Alfresco.util.Node(model.widgets[i].options.nodeRef) and var jsNode = AlfrescoUtil.getNodeDetails(model.widgets[i].options.nodeRef);
which were mentioned in another question here, but it seems like except of default values like, mimeType, size, nodeRef, I'm not able to use those to get data from the file.
These are my changes:
web-preview.get.js in -config folder of my custom media-viewer
//<import resource="classpath:/alfresco/templates/org/alfresco/import/alfresco-util.js">
if (model.widgets)
{
for (var i = 0; i < model.widgets.length; i++)
{
var at = "test";
//var jsNode = AlfrescoUtil.getNodeDetails(model.widgets[i].options.nodeRef);
//var author = jsNode.properties["cm:author"];
var widget = model.widgets[i];
if (widget.id == "WebPreview")
{
var conditions = [];
// Insert new pluginCondition(s) at start of the chain
conditions.push({
attributes: {
mimeType: "application/pdf"
},
plugins: [{
name: "PDF",
attributes: {
}
}]
});
var oldConditions = eval("(" + widget.options.pluginConditions + ")");
// Add the other conditions back in
for (var j = 0; j < oldConditions.length; j++)
{
conditions.push(oldConditions[j]);
}
// Override the original conditions
model.pluginConditions = jsonUtils.toJSONString(conditions);
widget.options.pluginConditions = model.pluginConditions;
}
}
}
PDF.js
/**
* Copyright (C) 2014 Will Abson
*/
/**
* This is the "PDF" plug-in used to display documents directly in the web browser.
*
* Supports the "application/pdf" mime types.
*
* #namespace Alfresco.WebPreview.prototype.Plugins
* #class Alfresco.WebPreview.prototype.Plugins.PDF
*/
(function()
{
/**
* PDF plug-in constructor
*
* #param wp {Alfresco.WebPreview} The Alfresco.WebPreview instance that decides which plugin to use
* #param attributes {Object} Arbitrary attributes brought in from the <plugin> element
*/
Alfresco.WebPreview.prototype.Plugins.PDF = function(wp, attributes)
{
this.wp = wp;
this.attributes = YAHOO.lang.merge(Alfresco.util.deepCopy(this.attributes), attributes);
//this.wp.options.nodeRef = this.wp.nodeRef;
return this;
};
Alfresco.WebPreview.prototype.Plugins.PDF.prototype =
{
/**
* Attributes
*/
attributes:
{
/**
* Maximum size to display given in bytes if the node's content is used.
* If the node content is larger than this value the image won't be displayed.
* Note! This doesn't apply if src is set to a thumbnail.
*
* #property srcMaxSize
* #type String
* #default "2000000"
*/
srcMaxSize: "2000000"
},
/**
* Tests if the plugin can be used in the users browser.
*
* #method report
* #return {String} Returns nothing if the plugin may be used, otherwise returns a message containing the reason
* it cant be used as a string.
* #public
*/
report: function PDF_report()
{
// TODO: Detect whether Adobe PDF plugin is installed, or if navigator is Chrome
// See https://stackoverflow.com/questions/185952/how-do-i-detect-the-adobe-acrobat-version-installed-in-firefox-via-javascript
var srcMaxSize = this.attributes.srcMaxSize;
if (!this.attributes.src && srcMaxSize.match(/^\d+$/) && this.wp.options.size > parseInt(srcMaxSize))
{
return this.wp.msg("pdf.tooLargeFile", this.wp.options.name, Alfresco.util.formatFileSize(this.wp.options.size), Alfresco.util.formatFileSize(this.attributes.srcMaxSize));
}
},
/**
* Display the node.
*
* #method display
* #public
*/
display: function PDF_display()
{
// TODO: Support rendering the content of the thumbnail specified
var src = this.wp.getContentUrl();
var test = this.attributes.author;
//var test = this.wp.options.nodeRef;
//var jsNode = new Alfresco.util.Node(test);
//var jsNode = AlfrescoUtil.getNodeDetails(this.wp.options.nodeRef);
//var author = jsNode.properties["cm:author"];
//var test = this.wp.options.author;
//var test1 = this.wp.options.mimeType;
//var test = this.attributes.author.replace(/[^\w_\-\. ]/g, "");
//.replace(/[^\w_\-\. ]/g, "");
return '<iframe name="' + test + '" src="' + src + '"></iframe>';
}
};
})();
As you can see by commented sections I tried different methods to access node properties/values, even simple strings, but I'm missing something for sure.
Thanks.
If you take a look at the source code you'll see that the helper method is nothing else than doing a remote call to var url = '/slingshot/doclib2/node/' + nodeRef.replace('://', '/');
So take a look at what that Repository WebScript is returning en match it to the properties you need.
I normally don't use this one and I know for sure the /api/metadata returns all the properties.

Is there an equivalent of setTimeout in Adobe's ExtendScript

Javascript's setTimeout function is a method of the window object. This object doesn't exist in ExtendScript and is therefore not available to scripts made for Adobe InDesign or Illustrator. What can I use instead to acheive the same results?
It's part of the extendscript's $ object.
$.sleep(1000) //tell extendscript to sleep 1000 milliseconds
Not the same as setTimeout() but you should be able to make it work for you.
EDIT: Here is setTimeout extension for extendscript:
$.setTimeout = function(func, time) {
$.sleep(time);
func();
};
$.setTimeout(function () {alert("hello world")}, 3000);
There is a setTimeout ExtendScript implementation here (German site, with code commented in english), but since it relies on app.idleTasks, it works in InDesign only (no Photoshop).
I paste here the downloadable code found in the link above (© Andreas Imhof):
/**
* setTimeout
* Version 1.0
* A setTimeout function implementation for InDesign ExtendScript like known from a Browser's Javascript.
* Uses InDesign's idleTask stuff.
* Timeout milliseconds are not accurate, but it allows to call a heavy load script,
* split it up into small junks for InDesign is not blocked too long and has time to breath.
*
* The script MUST run in its dedicated target engine:
* #target "InDesign"
* #targetengine "myOwnEngineName"
*
* DISCLAIMER:
* No warranty - use as is or modify but retain the originator's coordinates:
* CopyRight Andreas Imhof, www.aiedv.ch, ai#aiedv.ch
*/
//
var setTimeout_Task_curfile = new File($.fileName),
setTimeout_Task_curfullname = decodeURI(setTimeout_Task_curfile.name),
// setTimeout_Taskname must be a UNIQUE name, so we take it from the current running script!!
// May be set to any String like
// setTimeout_Taskname = 'myOwnTask';
setTimeout_Taskname = setTimeout_Task_curfullname.lastIndexOf(".") > 0 ? (setTimeout_Task_curfullname.substr(0,setTimeout_Task_curfullname.lastIndexOf("."))) : setTimeout_Task_curfullname,
setTimeout_Tasks = {}, // all defined tasks prepared to run
/**
* setTimeout_hasIdleTask
* Utility function
* #param {Number} the timeout task id
* #return {Boolean} true if a given timeout id also has his attached idleTask
*/
setTimeout_hasIdleTask = function(id) {
var has = false, i;
for (i = 0; i < app.idleTasks.length; i++) {
//alert("id: " + id + " tid: " + app.idleTasks[i].label);
if (app.idleTasks[i].isValid && (app.idleTasks[i].id === id)) {
has = true;
break;
}
}
return has;
},
/**
* setTimeoutList
* Utility function
* #return {String} a list of all currently active setTimeout_Tasks
*/
setTimeoutList = function() {
var list = "", cb,
k;
for (k in setTimeout_Tasks) {
if (list !== "") list += ",";
cb = setTimeout_Tasks[k]["cb"].toString();
cb = cb.replace(/\s/g,"");
list += setTimeout_Tasks[k]["taskid"] + ":" + cb;
}
return list;
},
/**
* idleTasksList
* Utility function
* #return {String} a list of all currently active idleTasks
*/
idleTasksList = function() {
var list = "",
k;
for (k = 0; k < app.idleTasks.length; k++) {
if (list !== "") list += ",";
list += app.idleTasks[k].id + ":" + setTimeout_hasIdleTask(app.idleTasks[k].id) + ":" + app.idleTasks[k].label;
}
return list;
},
/**
* setTimeoutInit
* Init/clean the timeout system
*/
setTimeoutInit = function() {
var it;
// remove all (erroneous) idleTasks
//alert("set idleTasks: " + app.idleTasks.length);
//NA: logmess("setTimeoutInit set idleTasks: " + app.idleTasks.length + "\n");
for (it = 0; it < app.idleTasks.length; it++) {
if (app.idleTasks[it].label == setTimeout_Taskname) {
//alert("removing idleTask id " + app.idleTasks[it].id + " label: " + app.idleTasks[it].label);
clearTimeout(app.idleTasks[it].id);
}
}
setTimeout_Tasks = {};
},
/**
* Tasks Handler
* Check if a task can be called now
* #param {Number} taskid
* #return {Boolean} always false
*/
setTimeoutHandler = function(taskid) {
var now_Ticks = new Date().getTime(),
cb, cb_retval = undefined;
try {
//alert("taskid: " + taskid + "\nnumcalls: " + setTimeout_Tasks[taskid]["numcalls"]);
// we look for well timed call only!!! CS6 calls at start AND after the timeout
if (setTimeout_Tasks[taskid]["end_ticks"] > now_Ticks) { // we have not reached timeout
//NA: logmess("setTimeoutHandler id " + taskid + " too early by ms: " + (setTimeout_Tasks[taskid]["end_ticks"] - now_Ticks) + "\n");
//alert("setTimeoutHandler id " + taskid + " too early by ms: " + (setTimeout_Tasks[taskid]["end_ticks"] - now_Ticks));
setTimeout_Tasks[taskid]["numcalls"] += 1;
return false; // wait for next call
}
}
catch(ex) {
alert("Exception (1) in function 'setTimeoutHandler()', taskid " + taskid + ":\n" + ex);
}
try {
cb = setTimeout_Tasks[taskid]["cb"]; // store the callback
clearTimeout(taskid); // remove the timeout
}
catch(ex) {
alert("Exception (2) in function 'setTimeoutHandler()', taskid " + taskid + ":\n" + ex);
}
try {
//NA: logmess("setTimeoutHandler call " + cb + "\n");
cb_retval = cb(); // call the cb
//if (cb_retval) alert("cb_retval:\n" + cb_retval);
} catch(ex) {
alert("Exception in function '" + cb() + ":\n" + ex);
}
return false;
},
/**
* setTimeout
* Set a function to called after the given timeout
* #param {function} callback the function to call
* #param {Number} timeout in ms
* #return {Boolean} null on error, otherwise the id (can be used with clearTimeout
*/
setTimeout = function(callback,timeout) {
try {
var idle_Task,
now_Ticks = new Date().getTime();
idle_Task = app.idleTasks.add({sleep:timeout});
idle_Task.label = setTimeout_Taskname;
setTimeout_Tasks[idle_Task.id] = {
"label": setTimeout_Taskname,
"start_ticks": now_Ticks,
"sleep": timeout,
"end_ticks": now_Ticks + timeout,
"cb": callback,
"taskid": idle_Task.id,
"numcalls": 0
};
setTimeout_Tasks[idle_Task.id].handler = function(ev){setTimeoutHandler(setTimeout_Tasks[idle_Task.id]["taskid"]);};
idle_Task.addEventListener(IdleEvent.ON_IDLE, setTimeout_Tasks[idle_Task.id].handler,false);
//NA: logmess("setTimeout idle_Task.id: " + idle_Task.id + ", timeout: " + timeout + "\ncallback: " + callback + "\n");
return idle_Task.id;
}
catch(ex) {
alert("Exception in function 'setTimeout()':\n" + ex);
}
return null;
},
/**
* clearTimeout
* Clear the timeout given by the setTimeout return value
* #param {Number} id the timeout id to clear
*/
clearTimeout = function (id){
var i, task = null;
for (i = 0; i < app.idleTasks.length; i++) {
//alert("id: " + id + " tid: " + app.idleTasks[i].label);
if ((app.idleTasks[i].id == id) && app.idleTasks[i].isValid) {
task = app.idleTasks[i];
break;
}
}
if (task !== null) {
try {
if (setTimeout_Tasks[id] && setTimeout_Tasks[id].handler) {
// this kills any!!! app.idleTasks.itemByID(id).removeEventListener(IdleEvent.ON_IDLE, setTimeout_Tasks[id].handler,false);
task.removeEventListener(IdleEvent.ON_IDLE, setTimeout_Tasks[id].handler,false);
}
// this kills any!!! app.idleTasks.itemByID(id).remove();
//task.remove();
task.sleep = 0;
}
catch(ex) {
alert("Exception in function 'clearTimeout() idleTasks':\n" + ex);
}
try {
delete setTimeout_Tasks[id];
}
catch(ex) {
alert("Exception in function 'clearTimeout() delete setTimeout_Tasks':\n" + ex);
}
}
};
/**
* Init/clean the timeout system
*/
setTimeoutInit();
// alert(setTimeout_Taskname); // Just to check if the 'setTimeout_Taskname' was set correctly
In Adobe After Effects using the $.sleep(time) will cause the script to stall.
Instead use the native app.scheduleTask(stringToExecute, delay, repeat) in ExtendScript:
Example:
var taskId = app.scheduleTask(function () {alert("hello world")}, 1500, false);
To cancel:
app.cancelTask(taskId);

Enumerate the properties of an AS3 object that may or may not be dynamic

In order to send a POST request I need to enumerate all properties of a given object. This object may or may not be dynamic. I'm looking for the most elegant solution. This is what I've got so far:
function createURLVariables(params:Object):URLVariables
{
// Workaround: Flash Player performs a GET if no params are passed
params ||= {forcePost: true};
var vars:URLVariables = new URLVariables();
var propertyName:String;
var propertyList:XMLList = describeType(params)..variable;
var propertyListLength:int = propertyList.length();
// A dynamic object won't return properties in this fashion
if (propertyListLength > 0)
{
for (var i:int; i < propertyListLength; i++)
{
propertyName = propertyList[i].#name;
vars[propertyName] = params[propertyName];
}
}
else
{
for (propertyName in params)
vars[propertyName] = params[propertyName];
}
return vars;
}
One potential problem is that this won't return properties for getters (accessors).
I took the following approach in the as3corelib JSON Encoder. You'll have to modify this to suit your needs, but it should give you an idea to work from. Note that there is some recursion in here (the convertToString call, which you might not need:
/**
* Converts an object to it's JSON string equivalent
*
* #param o The object to convert
* #return The JSON string representation of <code>o</code>
*/
private function objectToString( o:Object ):String
{
// create a string to store the object's jsonstring value
var s:String = "";
// determine if o is a class instance or a plain object
var classInfo:XML = describeType( o );
if ( classInfo.#name.toString() == "Object" )
{
// the value of o[key] in the loop below - store this
// as a variable so we don't have to keep looking up o[key]
// when testing for valid values to convert
var value:Object;
// loop over the keys in the object and add their converted
// values to the string
for ( var key:String in o )
{
// assign value to a variable for quick lookup
value = o[key];
// don't add function's to the JSON string
if ( value is Function )
{
// skip this key and try another
continue;
}
// when the length is 0 we're adding the first item so
// no comma is necessary
if ( s.length > 0 ) {
// we've already added an item, so add the comma separator
s += ","
}
s += escapeString( key ) + ":" + convertToString( value );
}
}
else // o is a class instance
{
// Loop over all of the variables and accessors in the class and
// serialize them along with their values.
for each ( var v:XML in classInfo..*.(
name() == "variable"
||
(
name() == "accessor"
// Issue #116 - Make sure accessors are readable
&& attribute( "access" ).charAt( 0 ) == "r" )
) )
{
// Issue #110 - If [Transient] metadata exists, then we should skip
if ( v.metadata && v.metadata.( #name == "Transient" ).length() > 0 )
{
continue;
}
// When the length is 0 we're adding the first item so
// no comma is necessary
if ( s.length > 0 ) {
// We've already added an item, so add the comma separator
s += ","
}
s += escapeString( v.#name.toString() ) + ":"
+ convertToString( o[ v.#name ] );
}
}
return "{" + s + "}";
}

How can i set values in cookie and get values from cookie not using with shared object in flex?

Hi I need to save email-id in my login form through the cookies. if I use shared object I am able to save but my requirement is need to save in cookies. How can I save? I got sample code from net. Attaching that code `package com {
import flash.external.ExternalInterface;
/**
* The Cookie class provides a simple way to create or access
* cookies in the embedding HTML document of the application.
*
*/
public class Cookie {
/**
* Flag if the class was properly initialized.
*/
private static var _initialized:Boolean = false;
/**
* Name of the cookie.
*/
private var _name:String;
/**
* Contents of the cookie.
*/
private var _value:String;
/**
* Flag indicating if a cookie was just created. It is <code>true</code>
* when the cookie did not exist before and <code>false</code> otherwise.
*/
private var _isNew:Boolean;
/**
* Name of the external javascript function used for getting
* cookie information.
*/
private static const GET_COOKIE:String = "cookieGetCookie";
/**
* Name of the external javascript function used for setting
* cookie information.
*/
private static const SET_COOKIE:String = "cookieSetCookie";
/**
* Javascript code to define the GET_COOKIE function.
*/
private static var FUNCTION_GET_COOKIE:String =
"function () { " +
"if (document." + GET_COOKIE + " == null) {" +
GET_COOKIE + " = function (name) { " +
"if (document.cookie) {" +
"cookies = document.cookie.split('; ');" +
"for (i = 0; i < cookies.length; i++) {" +
"param = cookies[i].split('=', 2);" +
"if (decodeURIComponent(param[0]) == name) {" +
"value = decodeURIComponent(param[1]);" +
"return value;" +
"}" +
"}" +
"}" +
"return null;" +
"};" +
"}" +
"}";
/**
* Javascript code to define the SET_COOKIE function.
*/
private static var FUNCTION_SET_COOKIE:String =
"function () { " +
"if (document." + SET_COOKIE + " == null) {" +
SET_COOKIE + " = function (name, value) { " +
"document.cookie = name + '=' + value;" +
"};" +
"}" +
"}";
/**
* Initializes the class by injecting javascript code into
* the embedding document. If the class was already initialized
* before, this method does nothing.
*/
private static function initialize():void {
if (Cookie._initialized) {
return;
}
if (!ExternalInterface.available) {
throw new Error("ExternalInterface is not available in this container. Internet Explorer ActiveX, Firefox, Mozilla 1.7.5 and greater, or other browsers that support NPRuntime are required.");
}
// Add functions to DOM if they aren't already there
ExternalInterface.call(FUNCTION_GET_COOKIE);
ExternalInterface.call(FUNCTION_SET_COOKIE);
Cookie._initialized = true;
}
/**
* Creates a new Cookie object. If a cookie with the specified
* name already exists, the existing value is used. Otherwise
* a new cookie is created as soon as a value is assigned to it.
*
* #param name The name of the cookie
*/
public function Cookie(name:String) {
Cookie.initialize();
this._name = name;
this._value = ExternalInterface.call(GET_COOKIE, name) as String;
this._isNew = this._value == null;
}
/**
* The name of the cookie.
*/
public function get name():String {
return this._name;
}
/**
* The value of the cookie. If it is a new cookie, it is not
* made persistent until a value is assigned to it.
*/
public function get value():String {
return this._value;
}
/**
* #private
*/
public function set value(value:String):void {
this._value = value;
ExternalInterface.call(SET_COOKIE, this._name, this._value);
}
/**
* The <code>isNew</code> property indicates if the cookie
* already exists or not.
*/
public function get isNew():Boolean {
return this._isNew;
}
}
}
`How can I use this? I need to integrate this with jsp by default swf file is embedded in jsp.
How can I save only username (email-id)? If user reenters again it should show in dropdown. how can I pass text input text into the cookie? Please help me in these Thanks in advance.
The posted code uses the ExternalInterface class to access the browsers cookies through JavaScript. I guess your question is about how to use that class. Here you go:
// Initialize the object:
var cookie:Cookie = new Cookie("NAME OF THE COOKIE");
// Retrieve and trace the value of the cookie:
trace(cookie.value());
// Set new value for the cookie:
cookie.value = "NEW VALUE";
I cannot answer the remaining questions about the drop down. You'll need to be more specific what your actual question is.

Resources