Skip to content

Instantly share code, notes, and snippets.

@velara3
Created February 13, 2017 22:16
Show Gist options
  • Save velara3/b2c17b5b729b0f2d6671515e1b45c0b0 to your computer and use it in GitHub Desktop.
Save velara3/b2c17b5b729b0f2d6671515e1b45c0b0 to your computer and use it in GitHub Desktop.
MXML Live Agent
<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
creationComplete="init()">
<fx:Script>
<![CDATA[
private var commandconnection:LocalConnection;
private function init():void
{
commandconnection = new LocalConnection();
commandconnection.allowDomain("*");
commandconnection.addEventListener(StatusEvent.STATUS, statusHandler);
}
private function statusHandler(event:Event):void
{
}
private function search():void
{
var mxmlFilter:FileFilter = new FileFilter("MXML", "*.mxml");
var f:File = new File();
f.browseForOpen("Choose MXML File", [ mxmlFilter ]);
f.addEventListener(Event.SELECT, selectHandler);
}
private function selectHandler(event:Event):void
{
var mxmlFile:File;
mxmlFile = event.target as File;
fileName.text = mxmlFile.nativePath;
}
private function watch():void
{
var mxmlFile:File;
mxmlFile = new File(fileName.text);
watching.text = "Watching " + fileName.text;
checkFile();
startTimer();
}
private var checkTimer:Timer = new Timer(1);
private function startTimer():void
{
checkTimer.delay = interval.value;
checkTimer.addEventListener(TimerEvent.TIMER, timerHandler);
checkTimer.start();
}
private function timerHandler(event:Event):void
{
checkFile();
}
private function intervalChanged():void
{
checkTimer.stop();
startTimer();
}
private var lastModifiedTime:Number = 0;
private function checkFile():void
{
var mxmlFile:File;
try {
mxmlFile = new File(fileName.text);
if (mxmlFile.modificationDate.time == lastModifiedTime)
return;
} catch (e:Error) {
// might check while file is open to be written so just ignore
// and check on the next interval;
return;
}
parseFile();
computeChanges();
applyChanges();
}
private var filter:Object = {
"http://ns.adobe.com/mxml/2009::Script": 1,
"http://ns.adobe.com/mxml/2009::Declarations": 1,
"http://ns.adobe.com/mxml/2009::Style": 1
}
private var supportedAttributes:Object = {
width: NaN,
height: NaN,
x: 0,
y: 0,
label: "",
style: "",
text: ""
}
private function getAttributeName(attrName:String):String
{
var cp:int = attrName.indexOf(".");
if (cp > -1)
attrName = attrName.substring(0, cp);
return attrName;
}
private function parseFile():void
{
var fs:FileStream = new FileStream();
var mxmlFile:File;
mxmlFile = new File(fileName.text);
fs.open(mxmlFile, FileMode.READ);
var xml:XML = new XML(fs.readUTFBytes(fs.bytesAvailable));
fs.close();
newDB = {};
generatedIDCounter = 0;
parseChildren(newDB, xml);
}
private var generatedIDCounter:int = 0;
// assume it is an instance if the tag name starts with a capital letter
private function isInstance(tagName:String):Boolean
{
var cp:int = tagName.indexOf("::");
if (cp > -1)
tagName = tagName.substring(cp + 2);
var c:String = tagName.charAt(0);
return c >= "A" && c <= "Z";
}
private function parseChildren(newDB:Object, parent:XML):void
{
var children:XMLList = parent.children();
var n:int = children.length();
for (var i:int = 0; i < n; i++)
{
var child:XML = children[i];
var childName:String = child.name();
if (childName == null)
continue; // saw this for CDATA children
if (filter[childName])
continue;
// we go deep first because that's how the Falcon compiler
// generates IDs for tags that don't have id attributes set.
parseChildren(newDB, child);
if (isInstance(childName))
{
var effectiveID:String = (child.@id.length() == 0) ?
"#" + generatedIDCounter++ :
child.@id;
var attrs:XMLList = child.attributes();
var m:int = attrs.length();
var attrMap:Object;
newDB[effectiveID] = attrMap = {};
for (var j:int = 0; j < m; j++)
{
var attrName:String = attrs[j].name();
if (supportedAttributes.hasOwnProperty(getAttributeName(attrName)))
attrMap[attrName] = child["@" + attrName].toString();
}
}
}
}
private var newDB:Object;
private var oldDB:Object;
private var changes:Object;
private var removals:Object;
private function computeChanges():void
{
changes = {};
removals = {};
if (oldDB == null)
{
oldDB = newDB;
return;
}
// assume set of components with ids and their ids won't change
for (var p:String in newDB)
{
var newValues:Object = newDB[p];
var oldValues:Object = oldDB[p];
for (var q:String in newValues)
{
var newValue:Object = newValues[q];
var oldValue:Object = oldValues[q];
if (newValue != oldValue)
{
var changeList:Object = changes[p];
if (!changeList)
changeList = changes[p] = {};
changeList[q] = newValue;
}
}
// look for deletions and set value back to default value
for (q in oldValues)
{
if (!newValues.hasOwnProperty(q))
{
var removeList:Object = removals[p];
if (!removeList)
removeList = removals[p] = {};
var propName:String = q;
if (q.indexOf(".") > -1)
{
var parts:Array = q.split(".");
propName = parts[0];
}
removeList[q] = supportedAttributes[propName];
}
}
}
oldDB = newDB;
}
private function applyChanges():void
{
for (var p:String in changes)
{
var changedValues:Object = changes[p];
for (var q:String in changedValues)
{
//setValue(id:String, property:String, value:Object):void
commandconnection.send("_MXMLLiveEditPluginCommands", "setValue", p, q, changedValues[q]);
}
}
for (p in removals)
{
var removedValues:Object = removals[p];
for (q in removedValues)
{
commandconnection.send("_MXMLLiveEditPluginCommands", "setValue", p, q, removedValues[q]);
}
}
}
]]>
</fx:Script>
<s:layout>
<s:VerticalLayout />
</s:layout>
<s:HGroup verticalAlign="middle">
<s:Label text="File to watch: "/>
<s:TextInput id="fileName" />
<s:Button label="Search..." click="search()" />
<s:Button label="Watch" click="watch()"/>
</s:HGroup>
<s:Label id="watching" />
<s:HGroup verticalAlign="middle">
<s:Label text="Check every " />
<s:NumericStepper id="interval" minimum="1" value="1" change="intervalChanged()" />
<s:Label text="seconds." />
<s:Button label="Check Now" click="checkFile()"/>
</s:HGroup>
</s:WindowedApplication>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment