*
* The correct dateElementOrder is required by the parser to
* determine the expected order of the date elements in the
* string being parsed.
*/
dateElementOrder: "mdy",
/* Standard date and time format patterns */
formatPatterns: {
shortDate: "M/d/yyyy",
longDate: "dddd, MMMM dd, yyyy",
shortTime: "h:mm tt",
longTime: "h:mm:ss tt",
fullDateTime: "dddd, MMMM dd, yyyy h:mm:ss tt",
sortableDateTime: "yyyy-MM-ddTHH:mm:ss",
universalSortableDateTime: "yyyy-MM-dd HH:mm:ssZ",
rfc1123: "ddd, dd MMM yyyy HH:mm:ss GMT",
monthDay: "MMMM dd",
yearMonth: "MMMM, yyyy"
},
/**
* NOTE: If a string format is not parsing correctly, but
* you would expect it parse, the problem likely lies below.
*
* The following regex patterns control most of the string matching
* within the parser.
*
* The Month name and Day name patterns were automatically generated
* and in general should be (mostly) correct.
*
* Beyond the month and day name patterns are natural language strings.
* Example: "next", "today", "months"
*
* These natural language string may NOT be correct for this culture.
* If they are not correct, please translate and edit this file
* providing the correct regular expression pattern.
*
* If you modify this file, please post your revised CultureInfo file
* to the Datejs Forum located at http://www.datejs.com/forums/.
*
* Please mark the subject of the post with [CultureInfo]. Example:
* Subject: [CultureInfo] Translated "da-DK" Danish(Denmark)
*
* We will add the modified patterns to the master source files.
*
* As well, please review the list of "Future Strings" section below.
*/
regexPatterns: {
jan: /^jan(uary)?/i,
feb: /^feb(ruary)?/i,
mar: /^mar(ch)?/i,
apr: /^apr(il)?/i,
may: /^may/i,
jun: /^jun(e)?/i,
jul: /^jul(y)?/i,
aug: /^aug(ust)?/i,
sep: /^sep(t(ember)?)?/i,
oct: /^oct(ober)?/i,
nov: /^nov(ember)?/i,
dec: /^dec(ember)?/i,
sun: /^su(n(day)?)?/i,
mon: /^mo(n(day)?)?/i,
tue: /^tu(e(s(day)?)?)?/i,
wed: /^we(d(nesday)?)?/i,
thu: /^th(u(r(s(day)?)?)?)?/i,
fri: /^fr(i(day)?)?/i,
sat: /^sa(t(urday)?)?/i,
future: /^next/i,
past: /^last|past|prev(ious)?/i,
add: /^(\+|aft(er)?|from|hence)/i,
subtract: /^(\-|bef(ore)?|ago)/i,
yesterday: /^yes(terday)?/i,
today: /^t(od(ay)?)?/i,
tomorrow: /^tom(orrow)?/i,
now: /^n(ow)?/i,
millisecond: /^ms|milli(second)?s?/i,
second: /^sec(ond)?s?/i,
minute: /^mn|min(ute)?s?/i,
hour: /^h(our)?s?/i,
week: /^w(eek)?s?/i,
month: /^m(onth)?s?/i,
day: /^d(ay)?s?/i,
year: /^y(ear)?s?/i,
shortMeridian: /^(a|p)/i,
longMeridian: /^(a\.?m?\.?|p\.?m?\.?)/i,
timezone: /^((e(s|d)t|c(s|d)t|m(s|d)t|p(s|d)t)|((gmt)?\s*(\+|\-)\s*\d\d\d\d?)|gmt|utc)/i,
ordinalSuffix: /^\s*(st|nd|rd|th)/i,
timeContext: /^\s*(\:|a(?!u|p)|p)/i
},
timezones: [
{name:"UTC", offset:"-000"},
{name:"GMT", offset:"-000"},
{name:"EST", offset:"-0500"},
{name:"EDT", offset:"-0400"},
{name:"CST", offset:"-0600"},
{name:"CDT", offset:"-0500"},
{name:"MST", offset:"-0700"},
{name:"MDT", offset:"-0600"},
{name:"PST", offset:"-0800"},
{name:"PDT", offset:"-0700"}
]
};
var $D = Date,
$P = $D.prototype,
p = function(s, l) {
if (!l)
l = 2;
return ("000" + s).slice(l * -1);
};
/**
* Resets the time of this Date object to 12:00 AM (00:00), which is the
* start of the day.
* @return {Date} this
*/
$P.clearTime = function() {
this.setHours(0);
this.setMinutes(0);
this.setSeconds(0);
this.setMilliseconds(0);
return this;
};
/**
* Resets the time of this Date object to the current time ('now').
* @return {Date} this
*/
$P.setTimeToNow = function() {
var n = new Date();
this.setHours(n.getHours());
this.setMinutes(n.getMinutes());
this.setSeconds(n.getSeconds());
this.setMilliseconds(n.getMilliseconds());
return this;
};
/**
* Gets a date that is set to the current date. The time is set to the start
* of the day (00:00 or 12:00 AM).
* @return {Date} The current date.
*/
$D.today = function() {
return new Date().clearTime();
};
/**
* Compares the first date to the second date and returns an number indication
* of their relative values.
* @param {Date} First Date object to compare [Required].
* @param {Date} Second Date object to compare to [Required].
* @return {Number} -1 = date1 is lessthan date2. 0 = values are equal.
* 1 = date1 is greaterthan date2.
*/
$D.compare = function(date1, date2) {
if (isNaN(date1) || isNaN(date2))
throw new Error(date1 + " - " + date2);
else if (date1 instanceof Date && date2 instanceof Date)
return (date1 < date2) ? -1 : (date1 > date2) ? 1 : 0;
else
throw new TypeError(date1 + " - " + date2);
};
/**
* Compares the first Date object to the second Date object and returns true
* if they are equal.
* @param {Date} First Date object to compare [Required]
* @param {Date} Second Date object to compare to [Required]
* @return {Boolean} true if dates are equal. false if they are not equal.
*/
$D.equals = function(date1, date2) {
return (date1.compareTo(date2) === 0);
};
/**
* Gets the day number (0-6) if given a CultureInfo specific string which is
* a valid dayName, abbreviatedDayName or shortestDayName (two char).
* @param {String} The name of the day (eg. "Monday, "Mon", "tuesday", "tue", "We", "we").
* @return {Number} The day number
*/
$D.getDayNumberFromName = function(name) {
var n = $C.dayNames,
m = $C.abbreviatedDayNames,
o = $C.shortestDayNames,
s = name.toLowerCase();
for (var i = 0; i < n.length; i++) {
if (n[i].toLowerCase() == s || m[i].toLowerCase() == s || o[i].toLowerCase() == s)
return i;
}
return -1;
};
/**
* Gets the month number (0-11) if given a Culture Info specific string which
* is a valid monthName or abbreviatedMonthName.
* @param {String} The name of the month (eg. "February, "Feb", "october", "oct").
* @return {Number} The day number
*/
$D.getMonthNumberFromName = function(name) {
var n = $C.monthNames,
m = $C.abbreviatedMonthNames,
s = name.toLowerCase();
for (var i = 0; i < n.length; i++) {
if (n[i].toLowerCase() == s || m[i].toLowerCase() == s)
return i;
}
return -1;
};
/**
* Determines if the current date instance is within a LeapYear.
* @param {Number} The year.
* @return {Boolean} true if date is within a LeapYear, otherwise false.
*/
$D.isLeapYear = function(year) {
return ((year % 4 === 0 && year % 100 !== 0) || year % 400 === 0);
};
/**
* Gets the number of days in the month, given a year and month value.
* Automatically corrects for LeapYear.
* @param {Number} The year.
* @param {Number} The month (0-11).
* @return {Number} The number of days in the month.
*/
$D.getDaysInMonth = function(year, month) {
return [31, ($D.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];
};
$D.getTimezoneAbbreviation = function(offset) {
var z = $C.timezones, p;
for (var i = 0; i < z.length; i++) {
if (z[i].offset === offset)
return z[i].name;
}
return null;
};
$D.getTimezoneOffset = function(name) {
var z = $C.timezones, p;
for (var i = 0; i < z.length; i++) {
if (z[i].name === name.toUpperCase())
return z[i].offset;
}
return null;
};
/**
* Returns a new Date object that is an exact date and time copy of the
* original instance.
* @return {Date} A new Date instance
*/
$P.clone = function() {
return new Date(this.getTime());
};
/**
* Compares this instance to a Date object and returns an number indication
* of their relative values.
* @param {Date} Date object to compare [Required]
* @return {Number} -1 = this is lessthan date. 0 = values are equal.
* 1 = this is greaterthan date.
*/
$P.compareTo = function(date) {
return Date.compare(this, date);
};
/**
* Compares this instance to another Date object and returns true if they are equal.
* @param {Date} Date object to compare. If no date to compare, new Date()
* [now] is used.
* @return {Boolean} true if dates are equal. false if they are not equal.
*/
$P.equals = function(date) {
return Date.equals(this, date || new Date());
};
/**
* Determines if this instance is between a range of two dates or equal to
* either the start or end dates.
* @param {Date} Start of range [Required]
* @param {Date} End of range [Required]
* @return {Boolean} true is this is between or equal to the start and end
* dates, else false
*/
$P.between = function(start, end) {
return this.getTime() >= start.getTime() && this.getTime() <= end.getTime();
};
/**
* Determines if this date occurs after the date to compare to.
* @param {Date} Date object to compare. If no date to compare, new Date()
* ("now") is used.
* @return {Boolean} true if this date instance is greater than the date to
* compare to (or "now"), otherwise false.
*/
$P.isAfter = function(date) {
return this.compareTo(date || new Date()) === 1;
};
/**
* Determines if this date occurs before the date to compare to.
* @param {Date} Date object to compare. If no date to compare, new Date()
* ("now") is used.
* @return {Boolean} true if this date instance is less than the date to
* compare to (or "now").
*/
$P.isBefore = function(date) {
return (this.compareTo(date || new Date()) === -1);
};
/**
* Determines if the current Date instance occurs on the same Date as the supplied 'date'.
* If no 'date' to compare to is provided, the current Date instance is compared to 'today'.
* @param {Date} Date object to compare. If no date to compare, the current Date ("now") is used.
* @return {Boolean} true if this Date instance occurs on the same Day as the supplied 'date'.
*/
$P.isToday = $P.isSameDay = function(date) {
return this.clone().clearTime().equals((date || new Date()).clone().clearTime());
};
/**
* Adds the specified number of milliseconds to this instance.
* @param {Number} The number of milliseconds to add. The number can be
* positive or negative [Required]
* @return {Date} this
*/
$P.addMilliseconds = function(value) {
this.setMilliseconds(this.getMilliseconds() + value * 1);
return this;
};
/**
* Adds the specified number of seconds to this instance.
* @param {Number} The number of seconds to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addSeconds = function(value) {
return this.addMilliseconds(value * 1000);
};
/**
* Adds the specified number of seconds to this instance.
* @param {Number} The number of seconds to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addMinutes = function(value) {
return this.addMilliseconds(value * 60000); /* 60*1000 */
};
/**
* Adds the specified number of hours to this instance.
* @param {Number} The number of hours to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addHours = function(value) {
return this.addMilliseconds(value * 3600000); /* 60*60*1000 */
};
/**
* Adds the specified number of days to this instance.
* @param {Number} The number of days to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addDays = function(value) {
this.setDate(this.getDate() + value * 1);
return this;
};
/**
* Adds the specified number of weeks to this instance.
* @param {Number} The number of weeks to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addWeeks = function(value) {
return this.addDays(value * 7);
};
/**
* Adds the specified number of months to this instance.
* @param {Number} The number of months to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addMonths = function(value) {
var n = this.getDate();
this.setDate(1);
this.setMonth(this.getMonth() + value * 1);
this.setDate(Math.min(n, $D.getDaysInMonth(this.getFullYear(), this.getMonth())));
return this;
};
/**
* Adds the specified number of years to this instance.
* @param {Number} The number of years to add. The number can be positive
* or negative [Required]
* @return {Date} this
*/
$P.addYears = function(value) {
return this.addMonths(value * 12);
};
/**
* Adds (or subtracts) to the value of the years, months, weeks, days, hours,
* minutes, seconds, milliseconds of the date instance using given configuration
* object. Positive and Negative values allowed.
* Example
* @param {Object} Configuration object containing attributes (months, days, etc.)
* @return {Date} this
*/
$P.add = function(config) {
if (typeof config == "number") {
this._orient = config;
return this;
}
var x = config;
if (x.milliseconds)
this.addMilliseconds(x.milliseconds);
if (x.seconds)
this.addSeconds(x.seconds);
if (x.minutes)
this.addMinutes(x.minutes);
if (x.hours)
this.addHours(x.hours);
if (x.weeks)
this.addWeeks(x.weeks);
if (x.months)
this.addMonths(x.months);
if (x.years)
this.addYears(x.years);
if (x.days)
this.addDays(x.days);
return this;
};
var $y, $m, $d;
/**
* Get the week number. Week one (1) is the week which contains the first
* Thursday of the year. Monday is considered the first day of the week.
* This algorithm is a JavaScript port of the work presented by Claus
* Tøndering at http://www.tondering.dk/claus/cal/node8.html#SECTION00880000000000000000
* .getWeek() Algorithm Copyright (c) 2008 Claus Tondering.
* The .getWeek() function does NOT convert the date to UTC. The local datetime
* is used. Please use .getISOWeek() to get the week of the UTC converted date.
* @return {Number} 1 to 53
*/
$P.getWeek = function() {
var a, b, c, d, e, f, g, n, s, w;
$y = (!$y) ? this.getFullYear() : $y;
$m = (!$m) ? this.getMonth() + 1 : $m;
$d = (!$d) ? this.getDate() : $d;
if ($m <= 2) {
a = $y - 1;
b = (a / 4 | 0) - (a / 100 | 0) + (a / 400 | 0);
c = ((a - 1) / 4 | 0) - ((a - 1) / 100 | 0) + ((a - 1) / 400 | 0);
s = b - c;
e = 0;
f = $d - 1 + (31 * ($m - 1));
} else {
a = $y;
b = (a / 4 | 0) - (a / 100 | 0) + (a / 400 | 0);
c = ((a - 1) / 4 | 0) - ((a - 1) / 100 | 0) + ((a - 1) / 400 | 0);
s = b - c;
e = s + 1;
f = $d + ((153 * ($m - 3) + 2) / 5) + 58 + s;
}
g = (a + b) % 7;
d = (f + g - e) % 7;
n = (f + 3 - d) | 0;
if (n < 0) {
w = 53 - ((g - s) / 5 | 0);
} else if (n > 364 + s) {
w = 1;
} else {
w = (n / 7 | 0) + 1;
}
$y = $m = $d = null;
return w;
};
/**
* Get the ISO 8601 week number. Week one ("01") is the week which contains the
* first Thursday of the year. Monday is considered the first day of the week.
* The .getISOWeek() function does convert the date to it's UTC value.
* Please use .getWeek() to get the week of the local date.
* @return {String} "01" to "53"
*/
$P.getISOWeek = function() {
$y = this.getUTCFullYear();
$m = this.getUTCMonth() + 1;
$d = this.getUTCDate();
return p(this.getWeek());
};
/**
* Moves the date to Monday of the week set. Week one (1) is the week which
* contains the first Thursday of the year.
* @param {Number} A Number (1 to 53) that represents the week of the year.
* @return {Date} this
*/
$P.setWeek = function(n) {
return this.moveToDayOfWeek(1).addWeeks(n - this.getWeek());
};
// private
var validate = function(n, min, max, name) {
if (typeof n == "undefined")
return false;
else if (typeof n != "number")
throw new TypeError(n + " is not a Number.");
else if (n < min || n > max)
throw new RangeError(n + " is not a valid value for " + name + ".");
return true;
};
/**
* Validates the number is within an acceptable range for milliseconds [0-999].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateMillisecond = function(value) {
return validate(value, 0, 999, "millisecond");
};
/**
* Validates the number is within an acceptable range for seconds [0-59].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateSecond = function(value) {
return validate(value, 0, 59, "second");
};
/**
* Validates the number is within an acceptable range for minutes [0-59].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateMinute = function(value) {
return validate(value, 0, 59, "minute");
};
/**
* Validates the number is within an acceptable range for hours [0-23].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateHour = function(value) {
return validate(value, 0, 23, "hour");
};
/**
* Validates the number is within an acceptable range for the days in a month
* [0 - MaxDaysInMonth].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateDay = function(value, year, month) {
return validate(value, 1, $D.getDaysInMonth(year, month), "day");
};
/**
* Validates the number is within an acceptable range for months [0-11].
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateMonth = function(value) {
return validate(value, 0, 11, "month");
};
/**
* Validates the number is within an acceptable range for years.
* @param {Number} The number to check if within range.
* @return {Boolean} true if within range, otherwise false.
*/
$D.validateYear = function(value) {
return validate(value, 0, 9999, "year");
};
/**
* Set the value of year, month, day, hour, minute, second, millisecond of
* date instance using given configuration object.
* Example
*
* @param {Object} Configuration object containing attributes (month, day, etc.)
* @return {Date} this
*/
$P.set = function(config) {
if ($D.validateMillisecond(config.millisecond))
this.addMilliseconds(config.millisecond - this.getMilliseconds());
if ($D.validateSecond(config.second))
this.addSeconds(config.second - this.getSeconds());
if ($D.validateMinute(config.minute))
this.addMinutes(config.minute - this.getMinutes());
if ($D.validateHour(config.hour))
this.addHours(config.hour - this.getHours());
if ($D.validateMonth(config.month))
this.addMonths(config.month - this.getMonth());
if ($D.validateYear(config.year))
this.addYears(config.year - this.getFullYear());
/* day has to go last because you can't validate the day without first knowing the month */
if ($D.validateDay(config.day, this.getFullYear(), this.getMonth()))
this.addDays(config.day - this.getDate());
if (config.timezone)
this.setTimezone(config.timezone);
if (config.timezoneOffset)
this.setTimezoneOffset(config.timezoneOffset);
if (config.week && validate(config.week, 0, 53, "week"))
this.setWeek(config.week);
return this;
};
/**
* Moves the date to the first day of the month.
* @return {Date} this
*/
$P.moveToFirstDayOfMonth = function() {
return this.set({ day: 1 });
};
/**
* Moves the date to the last day of the month.
* @return {Date} this
*/
$P.moveToLastDayOfMonth = function() {
return this.set({ day: $D.getDaysInMonth(this.getFullYear(), this.getMonth())});
};
/**
* Moves the date to the next n'th occurrence of the dayOfWeek starting from
* the beginning of the month. The number (-1) is a magic number and will return
* the last occurrence of the dayOfWeek in the month.
* @param {Number} The dayOfWeek to move to
* @param {Number} The n'th occurrence to move to. Use (-1) to return the
* last occurrence in the month
* @return {Date} this
*/
$P.moveToNthOccurrence = function(dayOfWeek, occurrence) {
var shift = 0;
if (occurrence > 0) {
shift = occurrence - 1;
}
else if (occurrence === -1) {
this.moveToLastDayOfMonth();
if (this.getDay() !== dayOfWeek)
this.moveToDayOfWeek(dayOfWeek, -1);
return this;
}
return this.moveToFirstDayOfMonth().addDays(-1)
.moveToDayOfWeek(dayOfWeek, +1).addWeeks(shift);
};
/**
* Move to the next or last dayOfWeek based on the orient value.
* @param {Number} The dayOfWeek to move to
* @param {Number} Forward (+1) or Back (-1). Defaults to +1. [Optional]
* @return {Date} this
*/
$P.moveToDayOfWeek = function(dayOfWeek, orient) {
var diff = (dayOfWeek - this.getDay() + 7 * (orient || +1)) % 7;
return this.addDays((diff === 0) ? diff += 7 * (orient || +1) : diff);
};
/**
* Move to the next or last month based on the orient value.
* @param {Number} The month to move to. 0 = January, 11 = December
* @param {Number} Forward (+1) or Back (-1). Defaults to +1. [Optional]
* @return {Date} this
*/
$P.moveToMonth = function(month, orient) {
var diff = (month - this.getMonth() + 12 * (orient || +1)) % 12;
return this.addMonths((diff === 0) ? diff += 12 * (orient || +1) : diff);
};
/**
* Get the Ordinal day (numeric day number) of the year, adjusted for leap year.
* @return {Number} 1 through 365 (366 in leap years)
*/
$P.getOrdinalNumber = function() {
return Math.ceil((this.clone().clearTime()
- new Date(this.getFullYear(), 0, 1)) / 86400000) + 1;
};
/**
* Get the time zone abbreviation of the current date.
* @return {String} The abbreviated time zone name (e.g. "EST")
*/
$P.getTimezone = function() {
return $D.getTimezoneAbbreviation(this.getUTCOffset());
};
$P.setTimezoneOffset = function(offset) {
var here = this.getTimezoneOffset(), there = Number(offset) * -6 / 10;
return this.addMinutes(there - here);
};
$P.setTimezone = function(offset) {
return this.setTimezoneOffset($D.getTimezoneOffset(offset));
};
/**
* Indicates whether Daylight Saving Time is observed in the current time zone.
* @return {Boolean} true|false
*/
$P.hasDaylightSavingTime = function() {
return (Date.today().set({month: 0, day: 1}).getTimezoneOffset()
!== Date.today().set({month: 6, day: 1}).getTimezoneOffset());
};
/**
* Indicates whether this Date instance is within the Daylight Saving Time
* range for the current time zone.
* @return {Boolean} true|false
*/
$P.isDaylightSavingTime = function() {
return Date.today().set({month: 0, day: 1}).getTimezoneOffset() != this.getTimezoneOffset();
};
/**
* Get the offset from UTC of the current date.
* @return {String} The 4-character offset string prefixed with + or - (e.g. "-0500")
*/
$P.getUTCOffset = function() {
var n = this.getTimezoneOffset() * -10 / 6, r;
if (n < 0) {
r = (n - 10000).toString();
return r.charAt(0) + r.substr(2);
}
else {
r = (n + 10000).toString();
return "+" + r.substr(1);
}
};
$P.getUTCTime = function() {
//Date.UTC(year, month[, date[, hrs[, min[, sec[, ms]]]]])
return Date.UTC(this.getUTCFullYear(), this.getUTCMonth(), this.getUTCDate(),
this.getUTCHours(), this.getUTCMinutes(), this.getUTCSeconds(),
this.getUTCMilliseconds());
};
/**
* Returns the number of milliseconds between this date and date.
* @param {Date} Defaults to now
* @return {Number} The diff in milliseconds
*/
$P.getElapsed = function(date) {
return (date || new Date()) - this;
};
if (!$P.toISOString) {
/**
* Converts the current date instance into a string with an ISO 8601 format.
* The date is converted to it's UTC value.
* @return {String} ISO 8601 string of date
*/
$P.toISOString = function() {
// From http://www.json.org/json.js. Public Domain.
function f(n) {
return n < 10 ? '0' + n : n;
}
return '"' + this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z"';
};
}
// private
$P._toString = $P.toString;
/**
* Converts the value of the current Date object to its equivalent string representation.
* Format Specifiers
CUSTOM DATE AND TIME FORMAT STRINGS
Format Description Example
------ --------------------------------------------------------------------------- -----------------------
s The seconds of the minute between 0-59. "0" to "59"
ss The seconds of the minute with leading zero if required. "00" to "59"
m The minute of the hour between 0-59. "0" or "59"
mm The minute of the hour with leading zero if required. "00" or "59"
h The hour of the day between 1-12. "1" to "12"
hh The hour of the day with leading zero if required. "01" to "12"
H The hour of the day between 0-23. "0" to "23"
HH The hour of the day with leading zero if required. "00" to "23"
d The day of the month between 1 and 31. "1" to "31"
dd The day of the month with leading zero if required. "01" to "31"
ddd Abbreviated day name. $C.abbreviatedDayNames. "Mon" to "Sun"
dddd The full day name. $C.dayNames. "Monday" to "Sunday"
M The month of the year between 1-12. "1" to "12"
MM The month of the year with leading zero if required. "01" to "12"
MMM Abbreviated month name. $C.abbreviatedMonthNames. "Jan" to "Dec"
MMMM The full month name. $C.monthNames. "January" to "December"
yy The year as a two-digit number. "99" or "08"
yyyy The full four digit year. "1999" or "2008"
t Displays the first character of the A.M./P.M. designator. "A" or "P"
$C.amDesignator or $C.pmDesignator
tt Displays the A.M./P.M. designator. "AM" or "PM"
$C.amDesignator or $C.pmDesignator
S The ordinal suffix ("st, "nd", "rd" or "th") of the current day. "st, "nd", "rd" or "th"
|| *Format* || *Description* || *Example* ||
|| d || The CultureInfo shortDate Format Pattern || "M/d/yyyy" ||
|| D || The CultureInfo longDate Format Pattern || "dddd, MMMM dd, yyyy" ||
|| F || The CultureInfo fullDateTime Format Pattern || "dddd, MMMM dd, yyyy h:mm:ss tt" ||
|| m || The CultureInfo monthDay Format Pattern || "MMMM dd" ||
|| r || The CultureInfo rfc1123 Format Pattern || "ddd, dd MMM yyyy HH:mm:ss GMT" ||
|| s || The CultureInfo sortableDateTime Format Pattern || "yyyy-MM-ddTHH:mm:ss" ||
|| t || The CultureInfo shortTime Format Pattern || "h:mm tt" ||
|| T || The CultureInfo longTime Format Pattern || "h:mm:ss tt" ||
|| u || The CultureInfo universalSortableDateTime Format Pattern || "yyyy-MM-dd HH:mm:ssZ" ||
|| y || The CultureInfo yearMonth Format Pattern || "MMMM, yyyy" ||
STANDARD DATE AND TIME FORMAT STRINGS
Format Description Example ("en-US")
------ --------------------------------------------------------------------------- -----------------------
d The CultureInfo shortDate Format Pattern "M/d/yyyy"
D The CultureInfo longDate Format Pattern "dddd, MMMM dd, yyyy"
F The CultureInfo fullDateTime Format Pattern "dddd, MMMM dd, yyyy h:mm:ss tt"
m The CultureInfo monthDay Format Pattern "MMMM dd"
r The CultureInfo rfc1123 Format Pattern "ddd, dd MMM yyyy HH:mm:ss GMT"
s The CultureInfo sortableDateTime Format Pattern "yyyy-MM-ddTHH:mm:ss"
t The CultureInfo shortTime Format Pattern "h:mm tt"
T The CultureInfo longTime Format Pattern "h:mm:ss tt"
u The CultureInfo universalSortableDateTime Format Pattern "yyyy-MM-dd HH:mm:ssZ"
y The CultureInfo yearMonth Format Pattern "MMMM, yyyy"
* @param {String} A format string consisting of one or more format spcifiers [Optional].
* @return {String} A string representation of the current Date object.
*/
$P.toString = function(format) {
var x = this;
// Standard Date and Time Format Strings. Formats pulled from CultureInfo file and
// may vary by culture.
if (format && format.length == 1) {
var c = $C.formatPatterns;
x.t = x.toString;
switch (format) {
case "d":
return x.t(c.shortDate);
case "D":
return x.t(c.longDate);
case "F":
return x.t(c.fullDateTime);
case "m":
return x.t(c.monthDay);
case "r":
return x.t(c.rfc1123);
case "s":
return x.t(c.sortableDateTime);
case "t":
return x.t(c.shortTime);
case "T":
return x.t(c.longTime);
case "u":
return x.t(c.universalSortableDateTime);
case "y":
return x.t(c.yearMonth);
}
}
var ord = function (n) {
switch (n * 1) {
case 1:
case 21:
case 31:
return "st";
case 2:
case 22:
return "nd";
case 3:
case 23:
return "rd";
default:
return "th";
}
};
return format ? format.replace(/(\\)?(dd?d?d?|MM?M?M?|yy?y?y?|hh?|HH?|mm?|ss?|tt?|S)/g,
function (m) {
if (m.charAt(0) === "\\") {
return m.replace("\\", "");
}
x.h = x.getHours;
switch (m) {
case "hh":
return p(x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12));
case "h":
return x.h() < 13 ? (x.h() === 0 ? 12 : x.h()) : (x.h() - 12);
case "HH":
return p(x.h());
case "H":
return x.h();
case "mm":
return p(x.getMinutes());
case "m":
return x.getMinutes();
case "ss":
return p(x.getSeconds());
case "s":
return x.getSeconds();
case "yyyy":
return p(x.getFullYear(), 4);
case "yy":
return p(x.getFullYear());
case "dddd":
return $C.dayNames[x.getDay()];
case "ddd":
return $C.abbreviatedDayNames[x.getDay()];
case "dd":
return p(x.getDate());
case "d":
return x.getDate();
case "MMMM":
return $C.monthNames[x.getMonth()];
case "MMM":
return $C.abbreviatedMonthNames[x.getMonth()];
case "MM":
return p((x.getMonth() + 1));
case "M":
return x.getMonth() + 1;
case "t":
return x.h() < 12 ? $C.amDesignator.substring(0, 1) : $C.pmDesignator.substring(0, 1);
case "tt":
return x.h() < 12 ? $C.amDesignator : $C.pmDesignator;
case "S":
return ord(x.getDate());
default:
return m;
}
}
) : this._toString();
};
}());
/**
* @class apf.layout
*
* Takes care of the spatial order of elements within the display area
* of the browser. Layouts can be saved to XML and loaded again. Window
* elements are dockable, which means the user can change the layout as s/he
* wishes. The state of the layout can be saved as XML at any time.
*
* #### Example
*
* This example shows five windows which have a layout defined in layout.xml.
*
* ```xml
*
*
*
*
*
*
*
*
* ```
*
* This is the layout file containing two layouts (_layout.xml_):
*
* ```xml
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* ```
*
* By binding on the _layout.xml_ you can easily create a layout manager.
*
* ```xml
*
*
*
*
*
*
*
*
*
*
*
*
* Add Layout
*
* ```
*
* @default_private
*/
// @todo a __WITH_DOM_REPARENTING should be added which can remove many of the functions of this element.
apf.layout = {
compile: function(oHtml) {
var l = this.layouts[oHtml.getAttribute("id")];
if (!l) return false;
var root = l.root.copy();//is there a point to copying?
l.layout.compile(root);
l.layout.reset();
},
removeAll: function(aData) {
aData.children.length = null
var htmlId = this.getHtmlId(aData.pHtml);
if (!this.rules[htmlId])
delete this.qlist[htmlId];
},
timer: null,
qlist: {},
dlist: [],
$hasQueue: false,
queue: function(oHtml, obj, compile, q) {
if (!q) {
this.$hasQueue = true;
q = this.qlist;
}
var id;
if (!(id = this.getHtmlId(oHtml)))
id = apf.setUniqueHtmlId(oHtml);
if (q[id]) {
if (obj)
q[id][2].push(obj);
if (compile)
q[id][1] = compile;
return;
}
q[id] = [oHtml, compile, [obj]];
if (!this.timer)
this.timer = apf.setZeroTimeout(function(){
apf.layout.processQueue();
});
},
processQueue: function(){
var i, id, l, qItem, list;
for (i = 0; i < this.dlist.length; i++) {
if (this.dlist[i].hidden)
this.dlist[i].hide();
else
this.dlist[i].show();
}
do {
var newq = {};
var qlist = this.qlist;
this.qlist = {};
this.$hasQueue = false;
for (id in qlist) {
qItem = qlist[id];
if (qItem[1])
apf.layout.compileAlignment(qItem[1]);
list = qItem[2];
for (i = 0, l = list.length; i < l; i++) {
if (list[i]) {
if (list[i].$amlDestroyed)
continue;
//if (list[i].$amlLoaded)
list[i].$updateLayout();
/*else
this.queue(qItem[0], list[i], null, newq);*/
}
}
apf.layout.activateRules(qItem[0]);
}
} while (this.$hasQueue);
if (apf.hasSingleRszEvent)
apf.layout.forceResize();
this.dlist = [];
apf.setZeroTimeout.clearTimeout(this.timer);
this.timer = null;
},
rules: {},
onresize: {},
getHtmlId: function(oHtml) {
return oHtml.getAttribute ? oHtml.getAttribute("id") : 1;
},
/**
* Adds layout rules to the resize event of the browser. Use this instead
* of `"onresize"` events to add rules that specify determine the layout.
* @param {HTMLElement} oHtml The element that triggers the execution of the rules.
* @param {String} id The identifier for the rules within the resize function of this element. Use this to easily update or remove the rules added.
* @param {String} rules The JavaScript code that is executed when the html element resizes.
* @param {Boolean} [overwrite] Whether the rules are added to the resize function or overwrite the previous set rules with the specified id.
*/
setRules: function(oHtml, id, rules, overwrite) {
if (!this.getHtmlId(oHtml))
apf.setUniqueHtmlId(oHtml);
if (!this.rules[this.getHtmlId(oHtml)])
this.rules[this.getHtmlId(oHtml)] = {};
var ruleset = this.rules[this.getHtmlId(oHtml)][id];
if (!overwrite && ruleset) {
this.rules[this.getHtmlId(oHtml)][id] = rules + "\n" + ruleset;
}
else
this.rules[this.getHtmlId(oHtml)][id] = rules;
},
/**
* Retrieves the rules set for the `"resize"` event of an HTML element specified by an identifier
* @param {HTMLElement} oHtml The element that triggers the execution of the rules.
* @param {String} id The identifier for the rules within the resize function of this element.
*/
getRules: function(oHtml, id) {
return id
? this.rules[this.getHtmlId(oHtml)][id]
: this.rules[this.getHtmlId(oHtml)];
},
/**
* Removes the rules set for the `"resize"` event of an html element specified by an identifier
* @param {HTMLElement} oHtml The element that triggers the execution of the rules.
* @param {String} id The identifier for the rules within the resize function of this element.
*/
removeRule: function(oHtml, id) {
var htmlId = this.getHtmlId(oHtml);
if (!this.rules[htmlId])
return;
var ret = this.rules[htmlId][id] || false;
delete this.rules[htmlId][id];
var prop;
for (prop in this.rules[htmlId]) {
}
if (!prop)
delete this.rules[htmlId]
if (apf.hasSingleRszEvent) {
if (this.onresize[htmlId])
this.onresize[htmlId] = null;
else {
var p = oHtml.parentNode;
while (p && p.nodeType == 1 && !this.onresize[p.getAttribute("id")]) {
p = p.parentNode;
}
if (p && p.nodeType == 1) {
var x = this.onresize[p.getAttribute("id")];
if (x.children)
delete x.children[htmlId]
}
}
}
return ret;
},
/**
* Activates the rules set for an HTML element
* @param {HTMLElement} oHtml The element that triggers the execution of the rules.
* @param {Boolean} [no_exec]
*/
activateRules: function(oHtml, no_exec) {
if (!oHtml) { //!apf.hasSingleRszEvent &&
var prop, obj;
for (prop in this.rules) {
obj = document.getElementById(prop);
if (!obj || obj.onresize) // || this.onresize[prop]
continue;
this.activateRules(obj);
}
if (apf.hasSingleRszEvent && apf.layout.$onresize)
apf.layout.$onresize();
return;
}
var rsz, id, rule, rules, strRules = [];
if (!apf.hasSingleRszEvent) {
rules = this.rules[this.getHtmlId(oHtml)];
if (!rules) {
oHtml.onresize = null;
return false;
}
for (id in rules) { //might need optimization using join()
if (typeof rules[id] != "string")
continue;
strRules.push(rules[id]);
}
//apf.console.info(strRules.join("\n"));
rsz = apf.needsCssPx
? new Function(strRules.join("\n"))
: new Function(strRules.join("\n").replace(/ \+ 'px'|try\{\}catch\(e\)\{\}\n/g,""))
oHtml.onresize = rsz;
if (!no_exec)
rsz();
}
else {
var htmlId = this.getHtmlId(oHtml);
rules = this.rules[htmlId];
if (!rules) {
//@todo keep .children
//delete this.onresize[htmlId];
return false;
}
for (id in rules) { //might need optimization using join()
if (typeof rules[id] != "string")
continue;
strRules.push(rules[id]);
}
var p = oHtml.parentNode;
while (p && p.nodeType == 1 && !this.onresize[p.getAttribute("id")]) {
p = p.parentNode;
}
var f = new Function(strRules.join("\n"));//.replace(/try\{/g, "").replace(/}catch\(e\)\{\s*\}/g, "\n")
if (this.onresize[htmlId])
f.children = this.onresize[htmlId].children;
if (p && p.nodeType == 1) {
var x = this.onresize[p.getAttribute("id")];
this.onresize[htmlId] = (x.children || (x.children = {}))[htmlId] = f;
}
else {
this.onresize[htmlId] = f;
}
if (!no_exec)
f();
if (!apf.layout.$onresize) {
var rsz = function(f) {
//@todo fix this
try{
var c = [];
for (var name in f)
if (f[name])
c.unshift(f[name]);
for (var i = 0; i < c.length; i++){
c[i]();
if (c[i].children) {
rsz(c[i].children);
}
}
}
catch (e) {
}
}
apf.addListener(window, "resize", apf.layout.$onresize = function(){
if (apf.config.resize !== false) {
rsz(apf.layout.onresize);
}
});
}
}
},
/**
* Forces calling the resize rules for an HTML element
* @param {HTMLElement} oHtml The element for which the rules are executed.
*/
forceResize: function(oHtml, force) {
if (!force) return;
if (apf.hasSingleRszEvent)
return apf.layout.$onresize && apf.layout.$onresize();
/* @todo this should be done recursive, old way for now
apf.hasSingleRszEvent
? this.onresize[this.getHtmlId(oHtml)]
:
*/
var rsz = oHtml.onresize;
if (rsz)
rsz();
var els = oHtml.getElementsByTagName("*");
for (var i = 0, l = els.length; i < l; i++) {
if (els[i] && els[i].onresize)
els[i].onresize();
}
},
paused: {},
/**
* Temporarily disables the resize rules for the HTML element.
* @param {HTMLElement} oHtml The element for which the rules are paused.
* @param {Function} func The resize code that is used temporarily for resize of the HTML element.
*/
pause: function(oHtml, replaceFunc) {
if (apf.hasSingleRszEvent) {
var htmlId = this.getHtmlId(oHtml);
this.paused[htmlId] = this.onresize[htmlId] || true;
if (replaceFunc) {
this.onresize[htmlId] = replaceFunc;
this.onresize[htmlId].children = this.paused[htmlId].children;
replaceFunc();
}
else
delete this.onresize[htmlId];
}
else {
this.paused[this.getHtmlId(oHtml)] = oHtml.onresize || true;
if (replaceFunc) {
oHtml.onresize = replaceFunc;
replaceFunc();
}
else
oHtml.onresize = null;
}
},
/**
* Enables paused resize rules for the HTML element
* @param {HTMLElement} oHtml The element for which the rules were paused.
*/
play: function(oHtml) {
if (!this.paused[this.getHtmlId(oHtml)])
return;
if (apf.hasSingleRszEvent) {
var htmlId = this.getHtmlId(oHtml);
var oldFunc = this.paused[htmlId];
if (typeof oldFunc == "function") {
this.onresize[htmlId] = oldFunc;
//oldFunc();
}
else
delete this.onresize[htmlId];
if (apf.layout.$onresize)
apf.layout.$onresize();
this.paused[this.getHtmlId(oHtml)] = null;
}
else {
var oldFunc = this.paused[this.getHtmlId(oHtml)];
if (typeof oldFunc == "function") {
oHtml.onresize = oldFunc;
oldFunc();
}
else
oHtml.onresize = null;
this.paused[this.getHtmlId(oHtml)] = null;
}
}
};
/**
* @private
*/
apf.getWindowWidth = function(){
return window.innerWidth;
};
/**
* @private
*/
apf.getWindowHeight = function(){
return window.innerHeight;
};
// Only add setZeroTimeout to the window object, and hide everything
// else in a closure.
apf.setZeroTimeout = !window.postMessage
? (function() {
function setZeroTimeout() {
return $setTimeout.apply(null, arguments);
}
setZeroTimeout.clearTimeout = function() {
return clearTimeout.apply(null, arguments);
};
return setZeroTimeout;
})()
: (function() {
var timeouts = [];
var messageName = "zero-timeout-message";
// Like setTimeout, but only takes a function argument. There's
// no time argument (always zero) and no arguments (you have to
// use a closure).
function setZeroTimeout(fn) {
var id = timeouts.push(fn);
window.postMessage(messageName, "*");
return id;
}
setZeroTimeout.clearTimeout = function(id) {
timeouts[id] = null;
}
function handleMessage(e) {
if (!e) e = event;
if (e.source == window && e.data == messageName) {
apf.stopPropagation(e);
if (timeouts.length > 0 && (t = timeouts.shift()))
t();
}
}
apf.addListener(window, "message", handleMessage, true);
// Add the one thing we want added to the window object.
return setZeroTimeout;
})();
/*
*
*/
apf.queue = {
//@todo apf3.0
q: {},
timer: null,
add: function(id, f) {
this.q[id] = f;
if (!this.timer)
this.timer = apf.setZeroTimeout(function(){
apf.queue.empty();
});
},
remove: function(id) {
delete this.q[id];
},
empty: function(prop) {
apf.setZeroTimeout.clearTimeout(this.timer);
this.timer = null;
if (apf.layout && apf.layout.$hasQueue)
apf.layout.processQueue();
if (apf.xmldb && apf.xmldb.$hasQueue)
apf.xmldb.notifyQueued();
var q = this.q;
this.q = {};
for (var prop in q) {
var f = q[prop];
if (f) {
delete q[prop];
f();
}
}
}
};
/**
*
* Controls the skinning modifications for AML.
*
* @private
*/
apf.skins = {
skins: {},
css: [],
// @TODO Doc these ?
events: ["onmousemove", "onmousedown", "onmouseup", "onmouseout",
"onclick", "ondragcopy", "ondragstart", "ondblclick"],
/* ***********
Init
************/
Init: function(xmlNode, refNode, path) {
/*
get data from refNode || xmlNode
- name
- icon-path
- media-path
all paths of the xmlNode are relative to the src attribute of refNode
all paths of the refNode are relative to the index.html
images/ is replaced if there is a refNode to the relative path from index to the skin + /images/
*/
var name = (refNode ? refNode.getAttribute("id") : null)
|| xmlNode.getAttribute("id");
var base = (refNode ? (refNode.getAttribute("src") || "").match(/\//) || path : "")
? (path || refNode.getAttribute("src")).replace(/\/[^\/]*$/, "") + "/"
: ""; //@todo make this absolute?
var mediaPath = null, iconPath = null;
mediaPath = xmlNode.getAttribute("media-path");
if (mediaPath !== null)
mediaPath = apf.getAbsolutePath(base || apf.hostPath, mediaPath);
else if (refNode) {
mediaPath = refNode.getAttribute("media-path");
if (mediaPath !== null)
mediaPath = apf.getAbsolutePath(apf.hostPath, mediaPath);
else
mediaPath = apf.getAbsolutePath(base || apf.hostPath, "images/");
}
iconPath = xmlNode.getAttribute("icon-path");
if (iconPath !== null)
iconPath = apf.getAbsolutePath(base || apf.hostPath, iconPath);
else if (refNode) {
iconPath = refNode.getAttribute("icon-path");
if (iconPath !== null)
iconPath = apf.getAbsolutePath(apf.hostPath, iconPath);
else
iconPath = apf.getAbsolutePath(base || apf.hostPath, "icons/");
}
if (!name)
name = "default";
if (xmlNode.getAttribute("id"))
document.body.className += " " + xmlNode.getAttribute("id");
var names = name.split("|");
name = names[0];
if (!this.skins[name] || name == "default") {
this.skins[name] = {
base: base,
name: name,
iconPath: iconPath,
mediaPath: mediaPath,
templates: {},
originals: {},
xml: xmlNode
}
if (names.length > 1) {
for (var i = 0; i < names.length; i++)
this.skins[names[i]] = this.skins[name];
}
}
if (!this.skins["default"] && this.$first == refNode)
this.skins["default"] = this.skins[name];
var nodes = xmlNode.childNodes;
for (var i = nodes.length - 1; i >= 0; i--) {
if (nodes[i].nodeType != 1)
continue;
//this.templates[nodes[i].tagName] = nodes[i];
this.skins[name].templates[nodes[i].getAttribute("name")] = nodes[i];
if (nodes[i].ownerDocument)
this.importSkinDef(nodes[i], base, name);
}
this.purgeCss(mediaPath, iconPath);
if (this.queue[name]) {
for (var prop in this.queue[name]) {
this.queue[name][prop]();
}
}
},
/**
* Loads a stylesheet from a URL.
* @param {String} filename The url to load the stylesheet from
* @param {String} [title] Title of the stylesheet to load
* @method loadStylesheet
*/
loadStylesheet: function(filename, title) {
var o;
with (o = document.getElementsByTagName("head")[0].appendChild(document.createElement("LINK"))) {
rel = "stylesheet";
type = "text/css";
href = filename;
title = title;
}
return o;
},
/* ***********
Import
************/
importSkinDef: function(xmlNode, basepath, name) {
var i, l, nodes = $xmlns(xmlNode, "style", apf.ns.aml), tnode, node;
for (i = 0, l = nodes.length; i < l; i++) {
node = nodes[i];
if (node.getAttribute("src"))
this.loadStylesheet(apf.getAbsolutePath(basepath, node.getAttribute("src")));
else {
var test = true;
if (node.getAttribute("condition")) {
try {
test = eval(node.getAttribute("condition"));
}
catch (e) {
test = false;
}
}
if (test) {
//#-ifndef __PROCESSED
tnode = node.firstChild;
while (tnode) {
this.css.push(tnode.nodeValue);
tnode = tnode.nextSibling;
}
/*#-else
this.css.push(nodes[i].firstChild.nodeValue);
#-endif*/
}
}
}
nodes = $xmlns(xmlNode, "alias", apf.ns.apf);
var t = this.skins[name].templates;
for (i = 0; i < nodes.length; i++) {
if (!nodes[i].firstChild)
continue;
t[nodes[i].firstChild.nodeValue.toLowerCase()] = xmlNode;
}
},
loadedCss: "",
purgeCss: function(imagepath, iconpath) {
if (!this.css.length)
return;
var cssString = this.css.join("\n").replace(/images\//g, imagepath).replace(/icons\//g, iconpath);
apf.preProcessCSS(cssString);
this.css = [];
},
loadCssInWindow: function(skinName, win, imagepath, iconpath) {
this.css = [];
var name = skinName.split(":");
var skin = this.skins[name[0]];
var template = skin.templates[name[1]];
this.importSkinDef(template, skin.base, skin.name);
var cssString = this.css.join("\n").replace(/images\//g, imagepath).replace(/icons\//g, iconpath);
apf.importCssString(cssString);
this.css = [];
},
/* ***********
Retrieve
************/
setSkinPaths: function(skinName, amlNode) {
skinName = skinName.split(":");
var name = skinName[0];
var type = skinName[1];
amlNode.iconPath = this.skins[name].iconPath;
amlNode.mediaPath = this.skins[name].mediaPath;
},
getTemplate: function(skinName, noError) {
skinName = skinName.split(":");
var name = skinName[0];
var type = skinName[1];
if (!this.skins[name]) {
if (noError)
return false;
return false;
}
if (!this.skins[name].templates[type])
return false;
var skin = this.skins[name].templates[type];
var originals = this.skins[name].originals[type];
if (!originals) {
originals = this.skins[name].originals[type] = {};
var nodes = $xmlns(skin, "presentation", apf.ns.aml)[0].childNodes;
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].nodeType != 1) continue;
originals[nodes[i].baseName || nodes[i][apf.TAGNAME]] = nodes[i];
}
}
/*for (var item in originals) {
pNodes[item] = originals[item];
}*/
return originals;
},
getCssString: function(skinName) {
return apf.queryValue($xmlns(this.skins[skinName.split(":")[0]].xml,
"style", apf.ns.aml)[0], "text()");
},
changeSkinset: function(value) {
var node = apf.document.documentElement;
while (node) {
if (node && node.nodeFunc == apf.NODE_VISIBLE
&& node.hasFeature(apf.__PRESENTATION__) && !node.skinset) {
node.$propHandlers["skinset"].call(node, value);//$forceSkinChange
node.skinset = null;
}
//Walk tree
if (node.firstChild || node.nextSibling) {
node = node.firstChild || node.nextSibling;
}
else {
do {
node = node.parentNode;
} while (node && !node.nextSibling)
if (node)
node = node.nextSibling;
}
}
},
queue: {},
waitForSkin: function(skinset, id, callback) {
if (this.skins[skinset])
return;
(this.queue[skinset] || (this.queue[skinset] = {}))[id] = callback;
return true;
},
setIcon: function(oHtml, strQuery, iconPath) {
if (!strQuery) {
oHtml.style.backgroundImage = "";
return;
}
if (oHtml.tagName.toLowerCase() == "img") {
oHtml.setAttribute("src", strQuery
? (iconPath || "") + strQuery
: "");
return;
}
//Assuming image url
{
var isQualified = strQuery.match(/^(https?|file):/);
oHtml.style.backgroundImage = "url(" + (isQualified ? "" : iconPath || "")
+ strQuery + ")";
}
}
};
/**
* Object handling sorting in a similar way as xslt.
*
* @constructor
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*
* @private
*/
apf.Sort = function(xmlNode) {
var settings = {};
//use this function to parse the each node
this.parseXml = function(xmlNode, clear) {
if (clear) settings = {};
settings.order = xmlNode.order;
settings.getValue = xmlNode.csort || xmlNode.$compile("sort");
settings.getNodes = self[xmlNode["nodes-method"]];
settings.ascending = (settings.order || "").indexOf("desc") == -1;
settings.order = null;
if (xmlNode["data-type"])
settings.method = sort_methods[xmlNode["data-type"]];
else if (xmlNode["sort-method"]) {
settings.method = self[xmlNode["sort-method"]];
}
else
settings.method = sort_methods["alpha"];
var str = xmlNode["date-format"];
if (str) {
settings.sort_dateFmtStr = str;
settings.method = sort_methods["date"];
var result = str.match(/(D+|Y+|M+|h+|m+|s+)/g);
if (result) {
for (var pos = {}, i = 0; i < result.length; i++)
pos[result[i].substr(0, 1)] = i + 1;
settings.dateFormat = new RegExp(str.replace(/([^\sDYMhms])/g, '\\$1')
.replace(/YYYY/, "(\\d\\d\\d\\d)")
.replace(/(DD|YY|MM|hh|mm|ss)/g, "(\\d\\d)"));
settings.dateReplace = "$" + pos["M"] + "/$" + pos["D"] + "/$" + pos["Y"];
if (pos["h"])
settings.dateReplace += " $" + pos["h"] + ":$" + pos["m"] + ":$" + pos["s"];
}
}
};
this.set = function(struct, clear) {
if (clear) settings = {};
apf.extend(settings, struct);
if (settings.ascending == undefined)
settings.ascending = struct.order
? struct.order.indexOf("desc") == -1
: true;
settings.order = null;
if (struct["type"])
settings.method = sort_methods[struct["type"]];
else if (struct["method"])
settings.method = self[struct["method"]];
else if (!settings.method)
settings.method = sort_methods["alpha"];
if (struct.format) {
settings.sort_dateFmtStr = struct.format;
//settings.method = sort_methods["date"];
var result = str.match(/(D+|Y+|M+|h+|m+|s+)/g);
if (result) {
for (var pos = {}, i = 0; i < result.length; i++)
pos[result[i].substr(0, 1)] = i + 1;
settings.dateFormat = new RegExp(str.replace(/([^\sDYMhms])/g, '\\$1')
.replace(/YYYY/, "(\\d\\d\\d\\d)")
.replace(/(DD|YY|MM|hh|mm|ss)/g, "(\\d\\d)"));
settings.dateReplace = "$" + pos["M"] + "/$" + pos["D"] + "/$" + pos["Y"];
if (pos["h"])
settings.dateReplace += " $" + pos["h"] + ":$" + pos["m"] + ":$" + pos["s"];
}
}
if (!settings.getValue) {
settings.getValue = function(item) {
return apf.queryValue(item, settings.xpath);
}
}
};
this.get = function(){
return apf.extend({}, settings);
};
//use this function in __xmlUpdate [this function isnt done yet]
this.findSortSibling = function(pNode, xmlNode) {
var nodes = getNodes ? getNodes(pNode, xmlNode) : this.getTraverseNodes(pNode);
for (var i = 0; i < nodes.length; i++)
if (!compare(xmlNode, nodes[i], true, sortSettings))
return nodes[i];
return null;
};
// Sorting methods for sort()
var sort_intmask = ["", "0", "00", "000", "0000", "00000", "000000",
"0000000", "00000000", "000000000", "0000000000", "00000000000",
"000000000000", "0000000000000", "00000000000000"];
var sort_methods = {
"alpha" : function (n) {
return n.toString().toLowerCase()
},
"number" : function (t) {
if (!t) var t = 0;
return (t.length < sort_intmask.length
? sort_intmask[sort_intmask.length - t.length]
: "") + t;
},
"date" : function (t, args) {
var sort_dateFormat = settings.dateFormat;
var sort_dateReplace = settings.dateReplace;
var sort_dateFmtStr = settings.sort_dateFmtStr;
var d;//|| (args && sort_dateFmtStr != args[0])
if (!sort_dateFormat) {
d = new Date(t);
}
else if (sort_dateFmtStr == '*')
d = apf.date.getDateTime(t);
else
d = (new Date(t.replace(sort_dateFormat, sort_dateReplace))).getTime();
var t = "" + d.getTime();//parseInt(d);
if (t == "NaN")
t = "0";
return (t.length < sort_intmask.length ? sort_intmask[sort_intmask.length
- t.length] : "") + t;
}
};
/*
sort(xpath, sort_xpath, sort_alpha, boolDesc, from, len)
jsort(n,f,p,ps,sm,desc,sp,ep)
*/
//var order, xpath, type, method, getNodes, dateFormat, dateReplace, sort_dateFmtStr, getValue;
this.apply = function(n, args, func, start, len) {
var sa = [], i = n.length;
// build string-sortable list with sort method
while (i--) {
var v = settings.getValue(n[i]);
if (n)
sa[sa.length] = {
toString: function(){
return this.v;
},
xmlNode: n[i],
v: (settings.method || sort_methods.alpha)(v || "", args, n[i])
};
}
// sort it
sa.sort();
//iterate like foreach
var end = len ? Math.min(sa.length, start + len) : sa.length;
if (!start)
start = 0;
if (func) {
if (settings.ascending)
for (i = start; i < end; i++)
f(i, end, sa[i].xmlNode, sa[i].v);
else
for (i = end - 1; i >= start; i--)
f(end - i - 1, end, sa[i].xmlNode, sa[i].v);
}
else {
//this could be optimized by reusing n... time it later
var res = [];
if (settings.ascending)
for (i = start; i < end; i++)
res[res.length] = sa[i].xmlNode;
else
for (i = end - 1; i >= start; i--)
res[res.length] = sa[i].xmlNode;
return res;
}
};
if (xmlNode)
this.parseXml(xmlNode);
};
/**
* The library that is used for the animations inside elements.
*
* @class apf.tween
*
* @default_private
*/
apf.tween = (function(apf) {
var modules = {
//Animation Modules
left: function(oHtml, value) {
oHtml.style.left = value + PX;
},
right: function(oHtml, value) {
oHtml.style.left = "";
oHtml.style.right = value + PX;
},
top: function(oHtml, value) {
oHtml.style.top = value + PX;
},
bottom: function(oHtml, value) {
oHtml.style.top = "";
oHtml.style.bottom = value + PX;
},
width: function(oHtml, value, center) {
oHtml.style.width = value + PX;
},
height: function(oHtml, value, center) {
oHtml.style.height = value + PX;
},
scrollTop: function(oHtml, value, center) {
oHtml.scrollTop = value;
},
scrollLeft: function(oHtml, value, center) {
oHtml.scrollLeft = value;
},
paddingTop: function(oHtml, value, center) {
oHtml.style.paddingTop = value + "px";
},
boxFlex: function(oHtml, value, center) {
oHtml.style[apf.CSS_FLEX_PROP] = value;
},
boxFlexGrow: function(oHtml, value, center) {
oHtml.style[apf.CSS_FLEX_PROP + "-grow"] = value;
},
"height-rsz": function(oHtml, value, center) {
oHtml.style.height = value + PX;
if (apf.hasSingleResizeEvent && apf.layout.$onresize)
apf.layout.$onresize();
},
mwidth: function(oHtml, value, info) {
var diff = apf.getDiff(oHtml);
oHtml.style.width = value + PX;
oHtml.style.marginLeft = -1 * (value / 2 + (parseInt(apf.getStyle(oHtml,
"borderLeftWidth")) || diff[0]/2) + (info.margin || 0)) + PX;
},
mheight: function(oHtml, value, info) {
var diff = apf.getDiff(oHtml);
oHtml.style.height = value + PX;
oHtml.style.marginTop = (-1 * value / 2 - (parseInt(apf.getStyle(oHtml,
"borderTopWidth")) || diff[1]/2) + (info.margin || 0)) + PX;
},
scrollwidth: function(oHtml, value) {
oHtml.style.width = value + PX;
oHtml.scrollLeft = oHtml.scrollWidth;
},
scrollheight_old: function(oHtml, value) {
try {
oHtml.style.height = value + PX;
oHtml.scrollTop = oHtml.scrollHeight;
}
catch (e) {
alert(value)
}
},
scrollheight: function(oHtml, value, info) {
var diff = apf.getHeightDiff(oHtml),
oInt = info.$int || oHtml;
oHtml.style.height = Math.max((value + (info.diff || 0)), 0) + PX;
oInt.scrollTop = oInt.scrollHeight - oInt.offsetHeight - diff
+ (info.diff || 0) - (apf.isGecko ? 16 : 0); //@todo where does this 16 come from??
},
scrolltop: function(oHtml, value) {
oHtml.style.height = value + PX;
oHtml.style.top = (-1 * value - 2) + PX;
oHtml.scrollTop = 0;//oHtml.scrollHeight - oHtml.offsetHeight;
},
clipright: function(oHtml, value, center) {
oHtml.style.clip = "rect(auto, auto, auto, " + value + "px)";
oHtml.style.marginLeft = (-1 * value) + PX;
},
fade: function(oHtml, value) {
oHtml.style.opacity = value;
},
bgcolor: function(oHtml, value) {
oHtml.style.backgroundColor = value;
},
textcolor: function(oHtml, value) {
oHtml.style.color = value;
},
htmlcss: function(oHtml, value, obj) {
oHtml.style[obj.type] = value + (obj.needsPx ? PX : "");
},
transformscale: function(oHtml, value, obj) {
oHtml.style[obj.type] = SCALEA + parseFloat(value) + SCALEB;
},
transformrotate: function(oHtml, value, obj) {
oHtml.style[obj.type] = ROTATEA + parseFloat(value) + ROTATEB;
},
transformvalscale: function(value) {
return SCALEA + parseFloat(value) + SCALEB;
},
transformvalrotate: function(value) {
return ROTATEA + parseFloat(value) + ROTATEB;
}
};
var ID = "id",
PX = "px",
NUM = "number",
TRANSVAL = "transformval",
TRANSFORM = "transform",
SCALE = "scale",
SCALEA = "scale(",
ROTATEA = "rotate(",
SCALEB = ")",
ROTATEB = "deg)",
CSSTIMING = ["linear", "ease-in", "ease-out", "ease", "ease-in-out", "cubic-bezier"],
CSSPROPS = {
"left" : "left",
"right" : "right",
"top" : "top",
"bottom" : "bottom",
"width" : "width",
"height" : "height",
"scrollTop" : false,
"scrollLeft" : false,
"mwidth" : false,
"mheight" : false,
"scrollwidth" : false,
"scrollheight": false,
"fade" : "opacity",
"opacity" : "opacity",
"bgcolor" : "background-color",
"textcolor" : "color",
"transform" : "transform"
},
__pow = Math.pow,
__round = Math.round,
queue = {},
current= null,
setQueue = function(oHtml, stepFunction) {
var id = oHtml.getAttribute(ID);
if (!id) {
apf.setUniqueHtmlId(oHtml);
id = oHtml.getAttribute(ID);
}
if (!queue[id])
queue[id] = [];
queue[id].push(stepFunction);
if (queue[id].length == 1)
stepFunction(0);
},
nextQueue = function(oHtml) {
var q = queue[oHtml.getAttribute(ID)];
if (!q) return;
q.shift(); //Remove current step function
if (q.length)
q[0](0);
},
clearQueue = function(oHtml, bStop) {
var q = queue[oHtml.getAttribute(ID)];
if (!q) return;
if (bStop && current && current.control)
current.control.stop = true;
q.length = 0;
},
purgeQueue = function(oHtml) {
var id = oHtml.getAttribute(ID);
if (!id) {
apf.setUniqueHtmlId(oHtml);
id = oHtml.getAttribute(ID);
}
for (var i in queue) {
if (i == id)
queue[i] = [];
}
},
// @TODO Doc
/**
* Calculates all the steps of an animation between a
* begin and end value based on three tween strategies
*
* @method calcSteps
* @param func {Function}
* @param fromValue {String}
* @param fromValue {String}
* @param nrOfSteps {Number}
*/
calcSteps = function(func, fromValue, toValue, nrOfSteps) {
var i = 0,
l = nrOfSteps - 1,
steps = [fromValue];
// backward compatibility...
if (typeof func == NUM) {
if (!func)
func = apf.tween.linear;
else if (func == 1)
func = apf.tween.easeInCubic;
else if (func == 2)
func = apf.tween.easeOutCubic;
}
/*
func should have the following signature:
func(t, x_min, dx)
where 0 <= t <= 1, dx = x_max - x_min
easeInCubic: function(t, x_min, dx) {
return dx * pow(t, 3) + x_min;
}
*/
for (i = 0; i < l; ++i)
steps.push(func(i / nrOfSteps, fromValue, toValue - fromValue));
steps.push(toValue);
return steps;
},
// @TODO Doc
/**
* Calculates all the steps of an animation between a
* begin and end value for colors
*
* @method calcColorSteps
* @param animtype {Function}
* @param fromValue {String}
* @param fromValue {String}
* @param nrOfSteps {Number}
*/
calcColorSteps = function(animtype, fromValue, toValue, nrOfSteps) {
var d2, d1,
c = apf.color.colorshex,
a = parseInt((c[fromValue] || fromValue).slice(1), 16),
b = parseInt((c[toValue] || toValue).slice(1), 16),
i = 0,
out = [];
for (; i < nrOfSteps; i++){
d1 = i / (nrOfSteps - 1), d2 = 1 - d1;
out[out.length] = "#" + ("000000" +
((__round((a & 0xff) * d2 + (b & 0xff) * d1) & 0xff) |
(__round((a & 0xff00) * d2 + (b & 0xff00) * d1) & 0xff00) |
(__round((a & 0xff0000) * d2 + (b & 0xff0000) * d1) & 0xff0000)).toString(16)).slice(-6);
}
return out;
},
// @TODO Doc wtf is stop ?
/**
* Tweens a single property of a single element or HTML element from a
* start to an end value.
*
* #### Example
*
* ```javascript
* apf.tween.single(myDiv, {
* type : "left",
* from : 10,
* to : 100,
* anim : apf.tween.EASEIN
* });
* ```
*
* #### Example
*
* Multiple animations can be run after each other
* by calling this function multiple times.
*
* ```javascript
* apf.tween.single(myDiv, options).single(myDiv2, options2);
* ```
*
* @method single
* @param {DOMNode} oHtml The object to animate.
* @param {Object} info The animation settings. The following properties are available:
* - type ([[String]]): The property to be animated. These are predefined
* property handlers and can be added by adding a
* method to `apf.tween` with the name of the property
* modifier. There are several handlers available.
* - `"left"`: Sets the left position
* - `"right"`: Sets the right position
* - `"top"`: Sets the top position
* - `"bottom"`: Sets the bottom position
* - `"width"` : Sets the horizontal size
* - `"height"`: Sets the vertical size
* - `"scrollTop"`: Sets the scoll position
* - `"mwidth"` : Sets the width and the margin-left to width/2
* - `"mheight"` : Sets the height ant the margin-top to height/2
* - `"scrollwidth"`: Sets the width an sets the scroll to the maximum size
* - `"scrollheight"`: Sets the height an sets the scroll to the maximum size
* - `"scrolltop"` : Sets the height and the top as the negative height value
* - `"fade"` : Sets the opacity property
* - `"bgcolor"`: Sets the background color
* - `"textcolor"`: Sets the text color
* - from ([[Number]] or [[String]]): The start value of the animation
* - to ([[Number]] or [[String]]): The end value of the animation
* - [steps] ([[Number]]): The number of steps to divide the tween in
* - [interval] ([[Number]]): The time between each step
* - [anim] ([[Number]]): The distribution of change between the step over the entire animation.
* - [color] ([[Boolean]]): Specifies whether the specified values are colors
* - [userdata] (`Mixed`): Any data you would like to have available in your callback methods
* - [onfinish] ([[Function]]): A function that is called at the end of the animation
* - [oneach] ([[Function]]): A function that is called at each step of the animation
* - [control] ([[Object]]): An object that can stop the animation at any point
* Methods:
* stop set on the object passed .
*/
single = function(oHtml, info) {
info = apf.extend({steps: 10, interval: 5, anim: apf.tween.linear, control: {}}, info);
info.steps = Math.ceil(info.steps * apf.animSteps);
info.interval = Math.ceil(info.interval * apf.animInterval);
if (oHtml.nodeFunc > 100) {
info.$int = oHtml.$int;
oHtml = oHtml.$ext;
}
try { //@TODO hack where currentStyle is still undefined
if ("fixed|absolute|relative".indexOf(apf.getStyle(oHtml, "position")) == -1)
oHtml.style.position = "relative";
} catch (e) {}
var useCSSAnim = (false && apf.supportCSSAnim && apf.supportCSSTransition && CSSPROPS[info.type]),
isTransform = (info.type == TRANSFORM);
info.method = useCSSAnim ? info.type : isTransform
? modules[TRANSFORM + (info.subType || SCALE)]
: modules[info.type]
? modules[info.type]
: (info.needsPx = needsPix[info.type] || false)
? modules.htmlcss
: modules.htmlcss;
if (useCSSAnim) {
var type = CSSPROPS[info.type];
if (type === false)
return apf.tween;
info.type = type || info.type;
if (isTransform) {
if (!info.subType)
info.subType = SCALE;
info.type = apf.supportCSSAnim;
}
var transform = (isTransform)
? modules[TRANSVAL + (info.subType || SCALE)]
: null;
oHtml.style[info.type] = isTransform
? transform(info.from)
: info.from + (needsPix[info.type] ? PX : "");
$setTimeout(function() {
oHtml.style[info.type] = isTransform
? transform(info.to)
: info.to + (needsPix[info.type] ? PX : "");
oHtml.offsetTop; //force style recalc
oHtml.style[apf.cssPrefix + "Transition"] = info.type + " " + ((info.steps
* info.interval) / 1000) + "s "
+ CSSTIMING[info.anim || 0];
var f = function() {
if (info.onfinish)
info.onfinish(oHtml, info.userdata);
oHtml.style[apf.cssPrefix + "Transition"] = "";
oHtml.removeEventListener(apf.cssAnimEvent, f);
};
oHtml.addEventListener(apf.cssAnimEvent, f);
});
return apf.tween;
}
if (info.control) {
info.control.state = apf.tween.RUNNING;
info.control.stop = function(){
info.control.state = apf.tween.STOPPING;
clearQueue(oHtml);
if (info.onstop)
info.onstop(oHtml, info.userdata);
}
}
var steps = info.color
? calcColorSteps(info.anim, info.from, info.to, info.steps)
: calcSteps(info.anim, parseFloat(info.from), parseFloat(info.to), info.steps),
stepFunction = function(step) {
if (info.control && info.control.state) {
info.control.state = apf.tween.STOPPED;
return;
}
current = info;
if (info.onbeforeeach
&& info.onbeforeeach(oHtml, info.userdata) === false)
return;
try {
info.method(oHtml, steps[step], info);
}
catch (e) {}
if (info.oneach)
info.oneach(oHtml, info.userdata);
if (step < info.steps)
return $setTimeout(function(){stepFunction(step + 1)}, info.interval);
current = null;
if (info.control)
info.control.state = apf.tween.STOPPED;
if (info.onfinish)
info.onfinish(oHtml, info.userdata);
nextQueue(oHtml);
};
if (info.type.indexOf("scroll") > -1)
purgeQueue(oHtml);
setQueue(oHtml, stepFunction);
return apf.tween;
},
// @TODO Doc wtf is stop
/**
* Tweens multiple properties of a single element or html element from a
* start to an end value.
*
* #### Example
*
* Here we are, animating both the left and width at the same time:
*
* ```javascript
* apf.tween.multi(myDiv, {
* anim : apf.tween.EASEIN
* tweens : [{
* type : "left",
* from : 10,
* to : 100,
* },
* {
* type : "width",
* from : 100,
* to : 400,
* }]
* });
* ````
*
* #### Example
*
* Multiple animations can be run after each other
* by calling this function multiple times.
*
* ```javascript
* apf.tween.multi(myDiv, options).multi(myDiv2, options2);
* ```
*
* @method multi
* @param {DOMNode} oHtml The object to animate.
* @param {Object} info The settings of the animation. It contains the following properties:
* - [steps] ([[Number]]): The number of steps to divide the tween in
* - [interval] ([[Number]]): The time between each step
* - [anim] ([[Number]]): The distribution of change between the step over
* the entire animation
* - [onfinish] ([[Function]]): A function that is called at the end of the animation
* - [oneach] ([[Function]]): A function that is called at each step of the animation
* - [oHtml] ([[HTMLElement]]): Another HTML element to animate.
* - [control] ([[Object]]): An object that can stop the animation at any point. It contains the following properties:
* - stop ([[Boolean]]): Specifies whether the animation should stop.
* - [tweens] ([[Array]]): A collection of simple objects specifying the single
* value animations that are to be executed simultaneously.
* (for the properties of these single tweens see the
* [[apf.tween.single]] method).
*/
multi = function(oHtml, info) {
info = apf.extend({steps: 10, interval: 5, anim: apf.tween.linear, control: {}}, info);
info.steps = Math.ceil(info.steps * apf.animSteps);
info.interval = Math.ceil(info.interval * apf.animInterval);
if (oHtml.nodeFunc > 100) {
info.$int = oHtml.$int;
oHtml = oHtml.$ext;
}
var animCSS, isTransform,
useCSSAnim = false && apf.supportCSSAnim && apf.supportCSSTransition,
hasCSSAnims = false,
cssDuration = ((info.steps * info.interval) / 1000),
cssAnim = CSSTIMING[info.anim || 0],
steps = [],
stepsTo = [],
i = 0,
l = info.tweens.length;
for (; i < l; i++) {
var data = info.tweens[i];
if (data.oHtml && data.oHtml.nodeFunc > 100) {
data.$int = data.oHtml.$int;
data.oHtml = data.oHtml.$ext;
}
animCSS = (useCSSAnim && CSSPROPS[data.type]);
isTransform = (data.type == TRANSFORM);
if (isTransform) {
if (!data.subType)
data.subType = SCALE;
data.type = apf.supportCSSAnim;
}
data.method = animCSS
? data.type
: isTransform
? modules[TRANSFORM + (data.subType)]
: modules[data.type]
? modules[data.type]
: (data.needsPx = needsPix[data.type] || false)
? modules.htmlcss
: modules.htmlcss;
if (animCSS) {
var type = isTransform ? data.type : CSSPROPS[data.type];
data.type = type || data.type;
var transform = modules[TRANSVAL + (data.subType)]
oHtml.style[data.type] = isTransform
? transform(data.from)
: data.from + (needsPix[data.type] ? PX : "");
stepsTo.push([data.type, isTransform
? transform(data.to)
: data.to + (needsPix[data.type] ? PX : "")]);
steps.push(data.type + " " + cssDuration + "s " + cssAnim + " 0");
hasCSSAnims = true;
}
else {
steps.push(data.color
? calcColorSteps(info.anim, data.from, data.to, info.steps)
: calcSteps(info.anim, parseFloat(data.from), parseFloat(data.to), info.steps));
}
}
if (hasCSSAnims) {
oHtml.style[apf.cssPrefix + "Transition"] = steps.join(",");
oHtml.offsetTop; //force style recalc
var count = 0,
func = function() {
count++;
if (count == stepsTo.length) {
if (info.onfinish)
info.onfinish(oHtml, info.userdata);
oHtml.style[apf.cssPrefix + "Transition"] = "";
oHtml.removeEventListener(apf.cssAnimEvent, func);
}
};
oHtml.addEventListener(apf.cssAnimEvent, func, false);
for (var k = 0, j = stepsTo.length; k < j; k++)
oHtml.style[stepsTo[k][0]] = stepsTo[k][1];
return apf.tween;
}
if (info.control) {
info.control.state = apf.tween.RUNNING;
info.control.stop = function(){
if (info.control.state == apf.tween.STOPPED)
return;
info.control.state = apf.tween.STOPPING;
clearQueue(oHtml);
if (info.onstop)
info.onstop(oHtml, info.userdata);
}
}
var tweens = info.tweens,
stepFunction = function(step) {
if (info.control && info.control.state) {
info.control.state = apf.tween.STOPPED;
return;
}
current = info;
try {
for (var i = 0; i < steps.length; i++) {
tweens[i].method(tweens[i].oHtml || oHtml,
steps[i][step], tweens[i]);
}
} catch (e) {}
if (info.oneach)
info.oneach(oHtml, info.userdata);
if (step < info.steps)
return $setTimeout(function(){stepFunction(step + 1)}, info.interval);
current = null;
if (info.control)
info.control.state = apf.tween.STOPPED;
if (info.onfinish)
info.onfinish(oHtml, info.userdata);
nextQueue(oHtml);
};
setQueue(oHtml, stepFunction);
return apf.tween;
},
/**
* Tweens an element or HTML element from its current state to a CSS class.
*
* #### Example
*
* Multiple animations can be run after each other by calling this function
* multiple times.
*
* ```javascript
* apf.tween.css(myDiv, 'class1').multi(myDiv2, 'class2');
* ```
*
* @method apf.tween.css
* @param {DOMNode} oHtml The object to animate.
* @param {String} className The class name that defines the CSS properties to be set or removed.
* @param {Object} info The settings of the animation. The following properties are available:
* Properties:
* - [steps] ([[Number]]): The number of steps to divide the tween in
* - [interval] ([[Number]]): The time between each step
* - [anim] ([[Number]]): The distribution of change between the step over the entire animation
* - [onfinish] ([[Function]]): A function that is called at the end of the animation
* - [oneach] ([[Function]]): A function that is called at each step of the animation
* - [control] ([[Object]]): An object that can stop the animation at any point. It contains the following property:
* - stop ([[Boolean]]): Specifies whether the animation should stop.
* @param {Boolean} remove Specifies whether the class is set or removed from the element
*/
css = function(oHtml, className, info, remove) {
(info = info || {}).tweens = [];
if (oHtml.nodeFunc > 100)
oHtml = oHtml.$ext;
if (remove)
apf.setStyleClass(oHtml, "", [className]);
var resetAnim = function(remove, callback) {
if (remove)
apf.setStyleClass(oHtml, "", [className]);
else
apf.setStyleClass(oHtml, className);
//Reset CSS values
for (var i = 0; i < info.tweens.length; i++){
if (info.tweens[i].type == "filter")
continue;
oHtml.style[info.tweens[i].type] = "";
}
if (callback)
callback.apply(this, arguments);
}
var onfinish = info.onfinish,
onstop = info.onstop;
info.onfinish = function(){resetAnim(remove, onfinish);}
info.onstop = function(){resetAnim(!remove, onstop);}
var result, newvalue, curvalue, j, isColor, style, rules, i,
tweens = {};
for (i = 0; i < document.styleSheets.length; i++) {
try { rules = document.styleSheets[i][apf.styleSheetRules]; }
catch(e) { rules = false; }
if (!rules || !rules.length)
continue;
for (j = rules.length - 1; j >= 0; j--) {
var rule = rules[j];
if (!rule.style || !(rule.selectorText || "").match("\." + className + "$"))
continue;
for (style in rule.style) {
if (!rule.style[style] || cssProps.indexOf("|" + style + "|") == -1)
continue;
if (style == "filter") {
if (!rule.style[style].match(/opacity\=([\d\.]+)/))
continue;
newvalue = RegExp.$1;
result = (apf.getStyleRecur(oHtml, style) || "")
.match(/opacity\=([\d\.]+)/);
curvalue = result ? RegExp.$1 : 100;
isColor = false;
if (newvalue == curvalue) {
if (remove) curvalue = 100;
else newvalue = 100;
}
}
else {
newvalue = remove && oHtml.style[style] || rule.style[style];
if (remove) oHtml.style[style] = "";
curvalue = apf.getStyleRecur(oHtml, style);
isColor = style.match(/color/i) ? true : false;
}
tweens[style] = {
type: style,
from: (isColor ? String : parseFloat)(remove
? newvalue
: curvalue),
to: (isColor ? String : parseFloat)(remove
? curvalue
: newvalue),
color: isColor,
needsPx: needsPix[style.toLowerCase()] || false
};
}
}
}
for (var prop in tweens)
info.tweens.push(tweens[prop]);
if (remove)
apf.setStyleClass(oHtml, className);
return multi(oHtml, info);
},
cssRemove = function(oHtml, className, info) {
css(oHtml, className, info, true);
},
needsPix = {
"left" : true,
"top" : true,
"bottom" : true,
"right" : true,
"width" : true,
"height" : true,
"fontSize" : true,
"lineHeight" : true,
"textIndent" : true,
"marginLeft" : true,
"marginTop" : true,
"marginRight" : true,
"marginBottom": true
},
cssProps = "|backgroundColor|backgroundPosition|color|width|filter"
+ "|height|left|top|bottom|right|fontSize"
+ "|letterSpacing|lineHeight|textIndent|opacity"
+ "|paddingLeft|paddingTop|paddingRight|paddingBottom"
+ "|borderLeftWidth|borderTopWidth|borderRightWidth|borderBottomWidth"
+ "|borderLeftColor|borderTopColor|borderRightColor|borderBottomColor"
+ "|marginLeft|marginTop|marginRight|marginBottom"
+ "|transform|", // transforms are special and get special treatment
cssTransforms = "|scale|rotate|";
return {
single: single,
multi: multi,
css: css,
cssRemove: cssRemove,
clearQueue: clearQueue,
addModule: function(name, func, force) {
if (typeof name != "string" || typeof func != "function" || (modules[name] && !force))
return this;
modules[name] = func;
return this;
},
/** Linear tweening method */
NORMAL: 0,
/** Ease-in tweening method */
EASEIN: 1,
/** Ease-out tweening method */
EASEOUT: 2,
RUNNING: 0,
STOPPING: 1,
STOPPED: 2,
calcColorSteps: calcColorSteps,
linear: function(t, x_min, dx) {
return dx * t + x_min;
},
easeInQuad: function(t, x_min, dx) {
return dx * __pow(t, 2) + x_min;
},
easeOutQuad: function(t, x_min, dx) {
return -dx * t * (t - 2) + x_min;
},
easeInOutQuad: function(t, x_min, dx) {
if ((t /= .5) < 1)
return dx / 2 * t * t + x_min;
return -dx / 2 * ((--t) * (t - 2) - 1) + x_min;
},
easeInCubic: function(t, x_min, dx) {
return dx * __pow(t, 3) + x_min;
},
easeOutCubic: function(t, x_min, dx) {
return dx * (__pow(t - 1, 3) + 1) + x_min;
},
easeInOutCubic: function(t, x_min, dx) {
if ((t /= .5) < 1)
return dx / 2 * __pow(t, 3) + x_min;
return dx / 2 * (__pow(t - 2, 3) + 2) + x_min;
},
easeInQuart: function(t, x_min, dx) {
return dx * __pow(t, 4) + x_min;
},
easeOutQuart: function(t, x_min, dx) {
return -dx * (__pow(t - 1, 4) - 1) + x_min;
},
easeInOutQuart: function(t, x_min, dx) {
if ((t /= .5) < 1)
return dx / 2 * __pow(t, 4) + x_min;
return -dx / 2 * (__pow(t - 2, 4) - 2) + x_min;
},
easeInQuint: function(t, x_min, dx) {
return dx * __pow(t, 5) + x_min;
},
easeOutQuint: function(t, x_min, dx) {
return dx * (__pow(t - 1, 5) + 1) + x_min;
},
easeInOutQuint: function(t, x_min, dx) {
if ((t /= .5) < 1)
return dx / 2 * __pow(t, 5) + x_min;
return dx / 2 * (__pow(t - 2, 5) + 2) + x_min;
},
easeInSine: function(t, x_min, dx) {
return -dx * Math.cos(t * (Math.PI / 2)) + dx + x_min;
},
easeOutSine: function(t, x_min, dx) {
return dx * Math.sin(t * (Math.PI / 2)) + x_min;
},
easeInOutSine: function(t, x_min, dx) {
return -dx / 2 * (Math.cos(Math.PI * t) - 1) + x_min;
},
easeInExpo: function(t, x_min, dx) {
return (t == 0) ? x_min : dx * __pow(2, 10 * (t - 1)) + x_min;
},
easeOutExpo: function(t, x_min, dx) {
return (t == 1) ? x_min + dx : dx * (-__pow(2, -10 * t) + 1) + x_min;
},
easeInOutExpo: function(t, x_min, dx) {
if (t == 0)
return x_min;
if (t == 1)
return x_min + dx;
if ((t /= .5) < 1)
return dx / 2 * __pow(2, 10 * (t - 1)) + x_min;
return dx / 2 * (-__pow(2, -10 * --t) + 2) + x_min;
},
easeInCirc: function(t, x_min, dx) {
return -dx * (Math.sqrt(1 - t * t) - 1) + x_min;
},
easeOutCirc: function(t, x_min, dx) {
return dx * Math.sqrt(1 - (t -= 1) * t) + x_min;
},
easeInOutCirc: function(t, x_min, dx) {
if ((t /= .5) < 1)
return -dx / 2 * (Math.sqrt(1 - t * t) - 1) + x_min;
return dx / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1) + x_min;
},
easeInElastic: function(t, x_min, dx) {
var s = 1.70158,
p = .3,
a = dx;
if (t == 0)
return x_min;
if (t == 1)
return x_min + dx;
if (!a || a < Math.abs(dx)) {
a = dx;
s = p / 4;
}
else
s = p / (2 * Math.PI) * Math.asin (dx / a);
return -(a * __pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)) + x_min;
},
easeOutElastic: function(t, x_min, dx) {
var s = 1.70158,
p = .3,
a = dx;
if (t == 0)
return x_min;
if (t == 1)
return x_min + dx;
if (a < Math.abs(dx)) {
a = dx;
s = p / 4;
}
else {
s = p / (2 * Math.PI) * Math.asin(dx / a);
}
return a * __pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + dx + x_min;
},
easeInOutElastic: function(t, x_min, dx) {
var s = 1.70158,
p = 0,
a = dx;
if (t == 0)
return x_min;
if ((t / 2) == 2)
return x_min + dx;
if (!p)
p = .3 * 1.5;
if (a < Math.abs(dx)) {
a = dx;
s = p / 4;
}
else {
s = p / (2 * Math.PI) * Math.asin(dx / a);
}
if (t < 1)
return -.5 * (a * __pow(2, 10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p)) + x_min;
return a * __pow(2, -10 * (t -= 1)) * Math.sin((t - s) * (2 * Math.PI) / p) * .5 + dx + x_min;
},
easeInBack: function(t, x_min, dx) {
var s = 1.70158;
return dx * __pow(t, 2) * ((s + 1) * t - s) + x_min;
},
easeOutBack: function(t, x_min, dx) {
var s = 1.70158;
return dx * ((t -= 1) * t * ((s + 1) * t + s) + 1) + x_min;
},
easeInOutBack: function(t, x_min, dx) {
var s = 1.70158;
if ((t / 2) < 1)
return dx / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + x_min;
return dx / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + x_min;
},
easeInBounce: function(t, x_min, dx) {
return dx - apf.tween.easeOutBounce(1 - t, 0, dx) + x_min;
},
easeOutBounce: function(t, x_min, dx) {
if (t < (1 / 2.75))
return dx * (7.5625 * t * t) + x_min;
else if (t < (2 / 2.75))
return dx * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + x_min;
else if (t < (2.5 / 2.75))
return dx * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + x_min;
else
return dx * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + x_min;
},
easeInOutBounce: function(t, x_min, dx) {
if (t < 1 / 2)
return apf.tween.easeInBounce(t * 2, 0, dx) * .5 + x_min;
return apf.tween.easeOutBounce(t * 2 - 1, 0, dx) * .5 + dx * .5 + x_min;
}
};
})(apf);
/**
* The XML database object provides local storage for XML data. This object
* routes all changes to the XML data to the data bound objects. It also
* provides utility functions for XML handling.
*
* @class apf.xmldb
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
* @additional
*
* @default_private
*/
apf.xmldb = new (function(){
var _self = this;
this.xmlDocTag = "a_doc";
this.xmlIdTag = "a_id";
this.xmlListenTag = "a_listen";
this.htmlIdTag = "id";
this.disableRDB = false;
this.$xmlDocLut = [];
this.$nodeCount = {};
var cleanRE = / (?:a_doc|a_id|a_listen|a_loaded)=(?:"|')[^'"]+(?:"|')/g,
whiteRE = />[\s\n\r\t]+= 0; i--) {
if (lut[ids[i]]) {
delete this.$listeners[ids[i]];
delete lut[ids[1]];
}
}
xmlNode.setAttribute(this.xmlListenTag, Object.keys(lut).join(";"));// + ";"
}
/*
* @todo Use this function when an element really unbinds from a
* piece of data and does not uses it for caching
* @private
*/
this.removeNodeListener = function(xmlNode, o, id) {
var listen = xmlNode.getAttribute(this.xmlListenTag);
var nodes = (listen ? listen.split(";") : []);
if (id && id.charAt(0) == "p") {
id = this.$listeners[id];
delete this.$listeners[id];
}
else {
id = "e" + o.$uniqueId;
}
for (var newnodes = [], i = 0; i < nodes.length; i++) {
if (nodes[i] != id)
newnodes.push(nodes[i]);
}
xmlNode.setAttribute(this.xmlListenTag, newnodes.join(";"));// + ";"
return xmlNode;
};
/**
* Sets the value of a text node. If the node doesn't exist, it is created.
*
* Changes are propagated to the databound elements listening for changes
* on the data changed.
*
* @param {XMLElement} pNode The parent of the text node.
* @param {String} value The value of the text node.
* @param {String} [xpath] The xpath statement which selects the text node.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.setTextNode =
apf.setTextNode = function(pNode, value, xpath, undoObj, range) {
var tNode;
if (xpath) {
tNode = pNode.selectSingleNode(xpath);
if (!tNode)
return;
pNode = tNode.nodeType == 1 ? tNode : null;
}
if (pNode.nodeType != 1)
tNode = pNode;
else if (pNode || !tNode) {
tNode = pNode.selectSingleNode("text()");
if (!tNode)
tNode = pNode.appendChild(pNode.ownerDocument.createTextNode(""));//createCDATASection
}
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.extra.oldValue = tNode.nodeValue;
undoObj.$filled = true;
}
//Apply Changes
if (range) { //@todo apf3.0 range
undoObj.extra.range = range;
}
else {
tNode.nodeValue = value;
if (tNode.$regbase)
tNode.$setValue(value);
}
this.applyChanges("text", tNode.parentNode, undoObj);
this.applyRDB(["setTextNode", pNode, value, xpath], undoObj || {xmlNode: pNode}); //@todo apf3.0 for range support
};
/**
* Sets an attribute on a node. Changes are propagated to the databound
* elements listening for changes on the data changed.
*
* @param {XMLElement} xmlNode The XML node to set the attribute on.
* @param {String} name The name of the attribute.
* @param {String} value The value of the attribute.
* @param {String} [xpath] The xpath statement to select the attribute.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.setAttribute =
apf.setAttribute = function(xmlNode, name, value, xpath, undoObj, range) {
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.name = name;
undoObj.$filled = true;
}
//Apply Changes
if (range) { //@todo apf3.0 range
undoObj.extra.range = range;
}
else
(xpath ? xmlNode.selectSingleNode(xpath) : xmlNode).setAttribute(name, value);
this.applyChanges("attribute", xmlNode, undoObj);
this.applyRDB(["setAttribute", xmlNode, name, value, xpath], undoObj || {xmlNode: xmlNode}); //@todo apf3.0 for range support
};
/**
* Removes an attribute of an XML node. Changes are propagated to the
* databound elements listening for changes on the data changed.
*
* @param {XMLElement} xmlNode The XML node to delete the attribute from
* @param {String} name The name of the attribute.
* @param {String} [xpath] The xpath statement to select the attribute.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.removeAttribute =
apf.removeAttribute = function(xmlNode, name, xpath, undoObj) {
//if(xmlNode.nodeType != 1) xmlNode.nodeValue = value;
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.name = name;
undoObj.$filled = true;
}
//Apply Changes
(xpath ? xmlNode.selectSingleNode(xpath) : xmlNode).removeAttribute(name);
this.applyChanges("attribute", xmlNode, undoObj);
this.applyRDB(["removeAttribute", xmlNode, name, xpath], undoObj || {xmlNode: xmlNode});
};
/**
* Replace one node with another. Changes are propagated to the
* databound elements listening for changes on the data changed.
*
* @param {XMLElement} oldNode The XML node to remove.
* @param {XMLElement} newNode The XML node to set.
* @param {String} [xpath] The xpath statement to select the attribute.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.replaceNode =
apf.replaceNode = function(newNode, oldNode, xpath, undoObj) {
//if(xmlNode.nodeType != 1) xmlNode.nodeValue = value;
//Apply Changes
if (xpath)
oldNode = oldNode.selectSingleNode(xpath);
// @todo: only do this once! - should store on the undo object
if (oldNode.ownerDocument.importNode && newNode.ownerDocument != oldNode.ownerDocument) {
var oldNodeS = newNode;
newNode = oldNode.ownerDocument.importNode(newNode, true); //Safari issue not auto importing nodes
if (oldNodeS.parentNode)
oldNodeS.parentNode.removeChild(oldNodeS);
}
this.applyRDB(["replaceNode", oldNode, this.cleanXml(newNode.xml), xpath], undoObj || {xmlNode: oldNode});
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.$filled = true;
undoObj.oldNode = oldNode;
undoObj.xmlNode = newNode;
}
this.cleanNode(newNode);
var parentNode = oldNode.parentNode;
if (!parentNode)
return;
parentNode.replaceChild(newNode, oldNode);
this.copyConnections(oldNode, newNode);
this.applyChanges("replacenode", newNode, undoObj);
return newNode;
};
/**
* Creates a new element under a parent XML node. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {XMLElement} pNode The parent XML node to add the new element to.
* @param {String} tagName The tagName of the {@link term.datanode data node} to add.
* @param {Array} attr list of the attributes to set. Each item is another array with the name and value.
* @param {XMLElement} beforeNode The XML node which indicates the insertion point.
* @param {String} [xpath] The xpath statement to select the attribute.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.addChildNode =
apf.addChildNode = function(pNode, tagName, attr, beforeNode, undoObj) {
//Create New Node
var xmlNode = pNode.insertBefore(pNode.ownerDocument
.createElement(tagName), beforeNode);
//Set Attributes
for (var i = 0; i < attr.length; i++)
xmlNode.setAttribute(attr[i][0], attr[i][1]);
//Action Tracker Support
if (undoObj && !undoObj.$filled) {
undoObj.extra.addedNode = xmlNode;
undoObj.$filled = true;
}
this.applyChanges("add", xmlNode, undoObj);
this.applyRDB(["addChildNode", pNode, tagName, attr, beforeNode], undoObj || {xmlNode: pNode});
return xmlNode;
};
/**
* Appends an XML node to a parent. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {XMLElement} pNode The parent XML node to add the element to.
* @param {XMLElement} xmlNode The XML node to insert.
* @param {XMLElement} beforeNode The XML node which indicates the insertion point.
* @param {Boolean} unique Specifies whether the parent can only contain one element with a certain tag name.
* @param {String} [xpath] The xpath statement to select the parent node.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.appendChild =
apf.appendChild = function(pNode, xmlNode, beforeNode, unique, xpath, undoObj) {
if (pNode == xmlNode.parentNode) //Shouldn't this be the same document?
return apf.xmldb.moveNode(pNode, xmlNode, beforeNode, null, xpath, undoObj);
if (unique && pNode.selectSingleNode(xmlNode.tagName))
return false;
// @todo: only do this once! - should store on the undo object
if (pNode.ownerDocument.importNode && pNode.ownerDocument != xmlNode.ownerDocument) {
var oldNode = xmlNode;
xmlNode = pNode.ownerDocument.importNode(xmlNode, true); //Safari issue not auto importing nodes
if (oldNode.parentNode)
oldNode.parentNode.removeChild(oldNode);
}
this.applyRDB(["appendChild", pNode, this.cleanXml(xmlNode.xml), beforeNode, unique, xpath], undoObj || {xmlNode: pNode});
//Add xmlNode to parent pNode or one selected by xpath statement
if (xpath) {
var addedNodes = [];
pNode = apf.createNodeFromXpath(pNode, xpath, addedNodes);
if (addedNodes.length) {
pNode.appendChild(xmlNode);
while (addedNodes.length) {
if (pNode == addedNodes.pop() && addedNodes.length)
pNode = pNode.parentNode;
}
}
}
else if (xmlNode.parentNode)
this.removeNode(xmlNode);
if (undoObj && !undoObj.$filled) {
undoObj.$filled = true;
this.cleanNode(xmlNode);
}
else
this.cleanNode(xmlNode);
pNode.insertBefore(xmlNode, beforeNode);
//detect if xmlNode should be removed somewhere else
//- [17-2-2004] changed pNode (2nd arg applychange) into xmlNode
this.applyChanges("add", xmlNode, undoObj);
return xmlNode;
};
/**
* Moves an XML node to a parent node. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {XMLElement} pNode The new parent XML node of the node.
* @param {XMLElement} xmlNode The XML node to move.
* @param {XMLElement} beforeNode The XML node which indicates the insertion point.
* @param {String} [xpath] The xpath statement to select the parent node.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.moveNode =
apf.moveNode = function(pNode, xmlNode, beforeNode, xpath, undoObj) {
//Action Tracker Support
if (!undoObj)
undoObj = {extra:{}};
undoObj.extra.oldParent = xmlNode.parentNode;
undoObj.extra.beforeNode = xmlNode.nextSibling;
undoObj.extra.parent = (xpath ? pNode.selectSingleNode(xpath) : pNode);
this.applyChanges("move-away", xmlNode, undoObj);
//Set new id if the node change document (for safari this should be fixed)
//@todo I don't get this if...
/*if (!apf.isWebkit
&& xmlNode.getAttribute(this.xmlIdTag)
&& apf.xmldb.getXmlDocId(xmlNode) != apf.xmldb.getXmlDocId(pNode)) {
xmlNode.removeAttribute(this.xmlIdTag));
this.nodeConnect(apf.xmldb.getXmlDocId(pNode), xmlNode);
}*/
// @todo: only do this once! - should store on the undo object
if (pNode.ownerDocument.importNode && pNode.ownerDocument != xmlNode.ownerDocument) {
var oldNode = xmlNode;
xmlNode = pNode.ownerDocument.importNode(xmlNode, true); //Safari issue not auto importing nodes
if (oldNode.parentNode)
oldNode.parentNode.removeChild(oldNode);
}
this.applyRDB(["moveNode", pNode, xmlNode, beforeNode, xpath], undoObj || {xmlNode: pNode}); //note: important that transport of rdb is async
undoObj.extra.parent.insertBefore(xmlNode, beforeNode);
this.applyChanges("move", xmlNode, undoObj);
};
/**
* Removes an XML node from its parent. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {XMLElement} xmlNode The XML node to remove from the dom tree.
* @param {String} [xpath] The xpath statement to select the parent node.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.removeNode =
apf.removeNode = function(xmlNode, xpath, undoObj) {
if (xpath)
xmlNode = xmlNode.selectSingleNode(xpath);
//ActionTracker Support
if (undoObj && !undoObj.$filled) {
undoObj.$filled = true;
undoObj.extra.parent = xmlNode.parentNode;
undoObj.extra.removedNode = xmlNode;
undoObj.extra.beforeNode = xmlNode.nextSibling;
}
this.applyRDB(["removeNode", xmlNode, xpath], undoObj || {xmlNode: xmlNode}); //note: important that transport of rdb is async
//Apply Changes
this.applyChanges("remove", xmlNode, undoObj);
var p = xmlNode.parentNode;
if (!p)
return;
p.removeChild(xmlNode);
this.applyChanges("redo-remove", xmlNode, null, p);//undoObj
//@todo clean xmlNode after removal??
};
/**
* Removes a list of XML nodes from their parent. Changes are propagated
* to the databound elements listening for changes on the data changed.
*
* @param {Array} xmlNodeList A list of XML nodes to remove.
* @param {apf.UndoData} [undoObj] The undo object that is responsible for archiving the changes.
*/
this.removeNodeList =
apf.removeNodeList = function(xmlNodeList, undoObj) {
this.applyRDB(["removeNodeList", xmlNodeList, null], undoObj || {xmlNode: p});
//if(xpath) xmlNode = xmlNode.selectSingleNode(xpath);
for (var rData = [], i = 0; i < xmlNodeList.length; i++) { //This can be optimized by looping nearer to xmlUpdate
//ActionTracker Support
if (undoObj) {
rData.push({
pNode: xmlNodeList[i].parentNode,
removedNode: xmlNodeList[i],
beforeNode: xmlNodeList[i].nextSibling
});
}
//Apply Changes
this.applyChanges("remove", xmlNodeList[i], undoObj);
var p = xmlNodeList[i].parentNode;
p.removeChild(xmlNodeList[i]);
this.applyChanges("redo-remove", xmlNodeList[i], null, p);//undoObj
}
if (undoObj && !undoObj.$filled) {
undoObj.$filled = true;
undoObj.extra.removeList = rData;
}
};
/*
* Looks for this.$listeners and executes their $xmlUpdate methods.
* @private
*/
var notifyQueue = {}, notifyTimer;
this.$hasQueue = false;
this.applyChanges = function(action, xmlNode, undoObj, nextloop) {
if (undoObj && undoObj.$dontapply) return;
if (undoObj && !undoObj.xmlNode) //@todo are we sure about this?
undoObj.xmlNode = xmlNode;
//Set Variables
var oParent = nextloop,
loopNode = (xmlNode.nodeType == 1 ? xmlNode : xmlNode.parentNode);
//var xmlId = xmlNode.getAttribute(this.xmlIdTag);
if (!this.delayUpdate && "|remove|move-away|".indexOf("|" + action + "|") > -1)
this.notifyQueued(); //empty queue
var listen, uId, uIds, i, j, hash, info, amlNode, runTimer, found, done = {};
while (loopNode && loopNode.nodeType == 1) {
//Get List of Node this.$listeners ID's
listen = loopNode.getAttribute(this.xmlListenTag);
if (listen) {
uIds = listen.split(";");
for (i = 0; i < uIds.length; i++) {
uId = uIds[i];
if (!uId || done[uId]) continue;
done[uId] = true;
//Property support
/*if (uId.charAt(0) == "p") {
uId = uId.split("|");
//@todo apf3.0 should this be exactly like in class.js?
//@todo optimize this to check the async flag: parsed[3] & 4
amlNode = apf.all[uId[1]]; //It's possible the aml node dissapeared in this loop.
if (amlNode) {
var model = apf.all[uId[3]];
var xpath = model.$propBinds[uId[1]][uId[2]].root;
amlNode.$execProperty(uId[2], xpath
? model.data.selectSingleNode(xpath)
: model.data);
}
continue;
}*/
hash = notifyQueue[uId];
if (!hash)
notifyQueue[uId] = hash = [];
// Filtering
if (!apf.isO3 && "|update|attribute|text|".indexOf("|" + action + "|") > -1) {
found = false;
for (j = 0; j < hash.length; j++) {
if (hash[j] && xmlNode == hash[j][1]
&& "|update|attribute|text|"
.indexOf("|" + hash[j][0] + "|") > -1) {
hash[j] = null;
found = true;
continue;
}
}
hash.push([action, xmlNode, loopNode, undoObj, oParent]);
runTimer = true;
continue;
}
//!this.delayUpdate && <- that doesnt work because of information that is destroyed
if (apf.isO3 || "|remove|move-away|move|add|".indexOf("|" + action + "|") > -1) {
if (this.$listeners[uId]) {
this.$listeners[uId]([action, xmlNode,
loopNode, undoObj, oParent]);
}
/*amlNode = apf.all[uId];
if (amlNode)
amlNode.$xmlUpdate(action, xmlNode,
loopNode, undoObj, oParent);*/
}
else {
hash.push([action, xmlNode, loopNode, undoObj, oParent]);
runTimer = true;
}
}
}
//Go one level up
loopNode = loopNode.parentNode || nextloop;
if (loopNode == nextloop)
nextloop = null;
}
if (true || undoObj && !this.delayUpdate) {
//Ok this was an action let's not delay execution
apf.xmldb.notifyQueued();
}
else if (runTimer) {
apf.setZeroTimeout.clearTimeout(notifyTimer);
//@todo find a better solution for this (at the end of a event stack unroll)
this.$hasQueue = true;
notifyTimer = apf.setZeroTimeout(function(){
//this.$hasQueue = true;
apf.xmldb.notifyQueued();
});
}
};
/*
* @todo in actiontracker - add stack auto purging
* - when undo item is purged which was a removed, remove cache item
* @todo shouldn't the removeNode method remove all this.$listeners?
* @todo rename to processQueue
* @private
*/
this.notifyQueued = function(){
this.$hasQueue = false;
var myQueue = notifyQueue;
notifyQueue = {};
apf.setZeroTimeout.clearTimeout(notifyTimer);
for (var uId in myQueue) {
if (!uId) continue;
var q = myQueue[uId];
var func = this.$listeners[uId];
//!amlNode ||
if (!q || !func)
continue;
//Run queue items
for (var i = 0; i < q.length; i++) {
if (!q[i])
continue;
//Update xml data
//amlNode.$xmlUpdate.apply(amlNode, q[i]);
func(q[i]);
}
}
};
/**
* @private
*/
this.notifyListeners = function(xmlNode) {
//This should be done recursive
var listen = xmlNode.getAttribute(apf.xmldb.xmlListenTag);
if (listen) {
listen = listen.split(";");
for (var j = 0; j < listen.length; j++) {
apf.all[listen[j]].$xmlUpdate("synchronize", xmlNode, xmlNode);
//load(xmlNode);
}
}
};
/*
* Sends Message through transport to tell remote databound this.$listeners
* that data has been changed
* @private
*/
this.applyRDB = function(args, undoObj) {
return;
var xmlNode = undoObj.localName || !undoObj.xmlNode
? args[1] && args[1].length && args[1][0] || args[1]
: undoObj.xmlNode;
if (xmlNode.nodeType == 2)
xmlNode = xmlNode.ownerElement || xmlNode.selectSingleNode("..");
var mdlId = apf.xmldb.getXmlDocId(xmlNode),
model = apf.nameserver.get("model", mdlId);
if (!model && apf.isO3)
model = self[mdlId];
if (!model) {
if (!apf.nameserver.getAll("remote").length)
return;
return;
}
if (!model.rdb) return;
var rdb = model.rdb;
// Add the messages to the undo object
if (undoObj.action)
rdb.$queueMessage(args, model, undoObj);
// Or send message now
else {
clearTimeout(rdb.queueTimer);
rdb.$queueMessage(args, model, rdb);
// use a timeout to batch consecutive calls into one RDB call
rdb.queueTimer = $setTimeout(function() {
rdb.$processQueue(rdb);
});
}
};
/**
* @private
*/
this.copyConnections = function(fromNode, toNode) {
//This should copy recursive
try {
toNode.setAttribute(this.xmlListenTag, fromNode.getAttribute(this.xmlListenTag));
}
catch (e) {}
try {
toNode.setAttribute(this.xmlIdTag, fromNode.getAttribute(this.xmlIdTag));
}
catch (e) {}
};
/**
* @private
*/
this.cleanXml = function(xml) {
if (typeof xml != "string")
return xml;
return xml.replace(cleanRE, "").replace(whiteRE, "><");
};
/**
* @private
*/
this.cleanNode = function(xmlNode) {
try {
var i, nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlListenTag + "]");
for (i = nodes.length - 1; i >= 0; i--)
nodes[i].removeAttribute(this.xmlListenTag);
nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlIdTag + "]");
for (i = nodes.length - 1; i >= 0; i--)
nodes[i].removeAttribute(this.xmlIdTag);
nodes = xmlNode.selectNodes("descendant-or-self::node()[@" + this.xmlDocTag + "]");
for (i = nodes.length - 1; i >= 0; i--)
nodes[i].removeAttribute(this.xmlDocTag);
nodes = xmlNode.selectNodes("descendant-or-self::node()[@a_loaded]");
for (i = nodes.length - 1; i >= 0; i--)
nodes[i].removeAttribute("a_loaded");
}
catch (e) {}
return xmlNode;
};
/**
* Returns a copy of the passed {@link term.datanode data node}. Bound
* data nodes contain special attributes to track them. These attributes
* are removed from the copied node when using this method.
*
* @param {XMLElement} xmlNode The {@link term.datanode data node} to copy.
* @return {XMLElement} The copy of the {@link term.datanode data node}.
*/
this.copy =
this.getCleanCopy =
apf.getCleanCopy = function(xmlNode) {
return apf.xmldb.cleanNode(xmlNode.cloneNode(true));
};
/**
* Unbind all APF Elements from a certain Form
* @private
*/
this.unbind = function(frm) {
//Loop through objects of all apf
for (var lookup = {}, i = 0; i < frm.apf.all.length; i++)
if (frm.apf.all[i] && frm.apf.all[i].unloadBindings)
lookup[frm.apf.all[i].unloadBindings()] = true;
//Remove Listen Nodes
for (var k = 0; k < this.$xmlDocLut.length; k++) {
if (!this.$xmlDocLut[k]) continue;
var Nodes = this.$xmlDocLut[k].selectNodes("//self::node()[@"
+ this.xmlListenTag + "]");
if (!Nodes) continue;
//Loop through Nodes and rebuild listen array
for (var i = 0; i < Nodes.length; i++) {
var listen = Nodes[i].getAttribute(this.xmlListenTag).split(";");
for (var nListen = [], j = 0; j < listen.length; j++)
if (!lookup[listen[j]])
nListen.push(listen[j]);
//Optimization??
if (nListen.length != listen.length)
Nodes[i].setAttribute(this.xmlListenTag, nListen.join(";"));
}
}
if (window.clearInterval)
window.clearInterval(this.$gcInterval);
};
/*
* @private
* @todo xml doc leakage
*/
this.getXmlDocId = function(xmlNode, model) {
var docEl = xmlNode.ownerDocument.documentElement;
if (!apf.isChildOf(docEl, xmlNode))
docEl = xmlNode;
var docId = (docEl || xmlNode).getAttribute(this.xmlDocTag)
|| this.$xmlDocLut.indexOf(docEl || xmlNode.ownerDocument || xmlNode);
if (model && apf.nameserver.get("model", docId) != model) {
docId = null;
docEl = xmlNode;
}
if (!docId || docId == -1) {
docId = this.$xmlDocLut.push(docEl || xmlNode.ownerDocument || xmlNode) - 1;
if (docEl)
docEl.setAttribute(this.xmlDocTag, String(docId));
}
if (model)
apf.nameserver.register("model", docId, model);
return docId;
};
});
/**
* The parser of the Ajax.org Markup Language. Besides aml this parser takes care
* of distributing parsing tasks to other parsers like the native html parser and
* the xsd parser.
* @parser
* @private
*
* @define include element that loads another aml files.
* Example:
*
*
*
* @attribute {String} src the location of the aml file to include in this application.
*
*/
apf.DOMParser = function(){};
apf.DOMParser.prototype = new (function(){
this.caseInsensitive = true;
this.preserveWhiteSpace = false; //@todo apf3.0 whitespace issue
this.$waitQueue = {}
this.$callCount = 0;
// privates
var RE = [
/\<\!(DOCTYPE|doctype)[^>]*>/,
/ /g,
/<\s*\/?\s*(?:\w+:\s*)[\w-]*[\s>\/]/g
],
XPATH = "//@*[not(contains(local-name(), '.')) and not(translate(local-name(), 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz') = local-name())]";
this.parseFromString = function(xmlStr, mimeType, options) {
var xmlNode;
if (this.caseInsensitive) {
//replace(/&\w+;/, ""). replace this by something else
//.replace(RE[1], " ")
var str = xmlStr.replace(RE[0], "")
.replace(RE[2], //.replace(/^[\r\n\s]*/, "")
function(m){ return m.toLowerCase(); });
/* @todo apf3.0 integrate this
x.ownerDocument.setProperty("SelectionNamespaces",
"xmlns:a='" + apf.ns.aml + "'");
*/
if (!this.supportNamespaces)
str = str.replace(/xmlns\=\"[^"]*\"/g, "");
var xmlNode = apf.getXmlDom(str);
if (apf.xmlParseError) apf.xmlParseError(xmlNode);
xmlNode = xmlNode.documentElement;
}
else {
xmlNode = apf.getXmlDom(xmlStr, null, this.preserveWhiteSpace || apf.debug).documentElement;
}
return this.parseFromXml(xmlNode, options);
};
//@todo prevent leakage by not recording .$aml
this.parseFromXml = function(xmlNode, options) {
var doc, docFrag, amlNode, beforeNode;
if (!options)
options = {};
if (!options.delayedRender && !options.include) {
//Create a new document
if (options.doc) {
doc = options.doc;
docFrag = options.docFrag || doc.createDocumentFragment();
}
else {
doc = new apf.AmlDocument();
doc.$aml = xmlNode;
doc.$domParser = this;
}
if (options.host)
doc.$parentNode = options.host; //This is for sub docs that need to access the outside tree
//Let's start building our tree
amlNode = this.$createNode(doc, xmlNode.nodeType, xmlNode); //Root node
(docFrag || doc).appendChild(amlNode);
if (options.htmlNode)
amlNode.$int = options.htmlNode;
}
else {
amlNode = options.amlNode;
doc = options.doc;
if (options.include) {
var n = amlNode.childNodes;
var p = n.indexOf(options.beforeNode);
var rest = p ? n.splice(p, n.length - p) : [];
}
}
//Set parse context
this.$parseContext = [amlNode, options];
this.$addParseState(amlNode, options || {});
//First pass - Node creation
var nodes, nodelist = {}, prios = [], _self = this;
var recur;
(recur = function(amlNode, nodes) {
var cL, newNode, node, nNodes,
cNodes = amlNode.childNodes,
i = 0,
l = nodes.length;
for (; i < l; i++) {
//Create child
newNode = _self.$createNode(doc, (node = nodes[i]).nodeType, node);
if (!newNode) continue; //for preserveWhiteSpace support
cNodes[cL = cNodes.length] = newNode; //Add to children
//Set tree refs
newNode.parentNode = amlNode;
if (cL > 0)
(newNode.previousSibling = cNodes[cL - 1]).nextSibling = newNode;
//Create children
if (!newNode.render && newNode.canHaveChildren && (nNodes = node.childNodes).length)
recur(newNode, nNodes);
//newNode.$aml = node; //@todo should be deprecated...
//Store high prio nodes for prio insertion
if (newNode.$parsePrio) {
if (newNode.$parsePrio == "001") {
newNode.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
continue;
}
(nodelist[newNode.$parsePrio] || (prios.push(newNode.$parsePrio)
&& (nodelist[newNode.$parsePrio] = []))).push(newNode); //for second pass
}
}
amlNode.firstChild = cNodes[0];
amlNode.lastChild = cNodes[cL];
})(amlNode, xmlNode.childNodes);
if (options.include && rest.length) {
var index = n.length - 1;
n.push.apply(n, rest);
var last = n[index];
var next = n[index + 1];
(next.previousSibling = last).nextSibling = next;
amlNode.lastChild = n[n.length - 1];
}
if (options.delay) {
amlNode.$parseOptions = {
prios: prios,
nodelist: nodelist
};
return (docFrag || doc);
}
//Second pass - Document Insert signalling
prios.sort();
var i, j, l, l2;
for (i = 0, l = prios.length; i < l; i++) {
nodes = nodelist[prios[i]];
for (j = 0, l2 = nodes.length; j < l2; j++) {
nodes[j].dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
}
}
if (this.$waitQueue[amlNode.$uniqueId]
&& this.$waitQueue[amlNode.$uniqueId].$shouldWait)
return (docFrag || doc);
if (options.timeout) {
$setTimeout(function(){
_self.$continueParsing(amlNode, options);
});
}
else {
this.$continueParsing(amlNode, options);
}
return (docFrag || doc);
};
this.$isPaused = function(amlNode) {
return this.$waitQueue[amlNode.$uniqueId] &&
this.$waitQueue[amlNode.$uniqueId].$shouldWait > 0;
}
this.$addParseState = function(amlNode, options) {
var waitQueue = this.$waitQueue[amlNode.$uniqueId]
|| (this.$waitQueue[amlNode.$uniqueId] = [])
waitQueue.pushUnique(options);
return waitQueue;
}
this.$pauseParsing = function(amlNode, options) {
var waitQueue = this.$waitQueue[amlNode.$uniqueId];
if (!waitQueue.$shouldWait) waitQueue.$shouldWait = 0;
waitQueue.$shouldWait++;
}
this.$continueParsing = function(amlNode, options) {
if (!amlNode)
amlNode = apf.document.documentElement;
var uId = amlNode.$uniqueId;
if (uId in this.$waitQueue) {
var item = this.$waitQueue[uId];
if (item.$shouldWait && --item.$shouldWait)
return false;
var node = amlNode.parentNode;
while (node && node.nodeType == 1) {
if (this.$waitQueue[node.$uniqueId]
&& this.$waitQueue[node.$uniqueId].$shouldWait)
return false;
node = node.parentNode;
}
var parseAmlNode = apf.all[uId];
delete this.$waitQueue[uId];
if (parseAmlNode) {
for (var i = 0; i < item.length; i++)
this.$parseState(parseAmlNode, item[i]);
}
//@todo Check for shouldWait here?
}
else
this.$parseState(amlNode, options || {});
delete this.$parseContext;
}
this.$parseState = function(amlNode, options) {
if (amlNode.$amlDestroyed)
return;
this.$callCount++;
if (amlNode.$parseOptions) {
var prios = amlNode.$parseOptions.prios,
nodelist = amlNode.$parseOptions.nodelist,
i, j, l, l2, node;
delete amlNode.$parseOptions;
//Second pass - Document Insert signalling
prios.sort();
for (i = 0, l = prios.length; i < l; i++) {
var nodes = nodelist[prios[i]];
for (j = 0, l2 = nodes.length; j < l2; j++) {
if (!(node = nodes[j]).parentNode || node.$amlLoaded) //@todo generalize this using compareDocumentPosition
continue;
nodes[j].dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
}
}
}
//instead of $amlLoaded use something more generic see compareDocumentPosition
if (!options.ignoreSelf && !amlNode.$amlLoaded)
amlNode.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
//Recursively signal non prio nodes
(function _recur(nodes) {
var node, nNodes;
for (var i = 0, l = nodes.length; i < l; i++) {
if (!(node = nodes[i]).$amlLoaded) {
node.dispatchEvent("DOMNodeInsertedIntoDocument"); //{relatedParent : nodes[j].parentNode}
}
//Create children
if (!node.render && (nNodes = node.childNodes).length)
_recur(nNodes);
}
})(amlNode.childNodes);
if (!--this.$callCount && !options.delay)
apf.queue.empty();
if (options.callback)
options.callback.call(amlNode.ownerDocument);
};
this.$createNode = function(doc, nodeType, xmlNode, namespaceURI, nodeName, nodeValue) {
var o;
switch (nodeType) {
case 1:
var id, prefix;
if (xmlNode) {
if ((namespaceURI = xmlNode.namespaceURI || apf.ns.xhtml)
&& !(prefix = doc.$prefixes[namespaceURI])) {
doc.$prefixes[prefix = xmlNode.prefix || xmlNode.scopeName || ""] = namespaceURI;
doc.$namespaceURIs[namespaceURI] = prefix;
if (!doc.namespaceURI && !prefix) {
doc.namespaceURI = namespaceURI;
doc.prefix = prefix;
}
}
nodeName = xmlNode.baseName || xmlNode.localName || xmlNode.tagName.split(":").pop();
}
else {
prefix = doc.$prefixes[namespaceURI] || "";
}
var els = apf.namespaces[namespaceURI].elements;
o = new (els[nodeName] || els["@default"])(null, nodeName);
o.prefix = prefix || "";
o.namespaceURI = namespaceURI;
o.tagName = prefix ? prefix + ":" + nodeName : nodeName;
if (xmlNode) {
if ((id = xmlNode.getAttribute("id")) && !self[id])
o.$propHandlers["id"].call(o, o.id = id);
//attributes
var attr = xmlNode.attributes, n;
for (var a, na, i = 0, l = attr.length; i < l; i++) {
o.attributes.push(na = new apf.AmlAttr(o,
(n = (a = attr[i]).nodeName), a.value));
if (n == "render")
o.render = true;
else
if (n.substr(0, 2) == "on")
na.$triggerUpdate();
}
}
break;
case 2:
o = new apf.AmlAttr();
o.name = o.nodeName = nodeName;
if (nodeValue || (nodeValue = xmlNode && xmlNode.nodeValue))
o.value = o.nodeValue = nodeValue;
if (xmlNode) {
if (xmlNode.namespaceURI && !(o.prefix = doc.$namespaceURIs[o.namespaceURI = xmlNode.namespaceURI]))
doc.$prefixes[o.prefix = xmlNode.prefix || xmlNode.scopeName] = o.namespaceURI;
}
else {
o.prefix = doc.$prefixes[namespaceURI];
}
break;
case 3:
if (xmlNode)
nodeValue = xmlNode && xmlNode.nodeValue;
if (!this.preserveWhiteSpace && !(nodeValue || "").trim())
return;
o = new apf.AmlText();
o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue;
break;
case 7:
var target = nodeName || xmlNode && xmlNode.nodeName;
o = new apf.aml.processingInstructions[target]();
o.target = o.nodeName = target;
o.data = o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue;
break;
case 4:
o = new apf.AmlCDATASection();
o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue;
break;
case 5: //unsupported
o = new apf.AmlNode();
o.nodeType = nodeType;
break;
case 6: //unsupported
o = new apf.AmlNode();
o.nodeType = nodeType;
break;
case 8:
o = new apf.AmlComment();
o.nodeValue = nodeValue || xmlNode && xmlNode.nodeValue;
break;
case 9:
o = new apf.AmlDocument();
o.$domParser = this;
break;
case 10: //unsupported
o = new apf.AmlNode();
o.nodeType = nodeType;
break;
case 11:
o = new apf.AmlDocumentFragment();
break;
}
o.ownerDocument = doc;
o.$aml = xmlNode;
return o;
};
})();
/**
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*/
apf.AmlNamespace = function(){
this.elements = {};
this.processingInstructions = {};
};
apf.AmlNamespace.prototype = {
setElement: function(tagName, fConstr) {
return this.elements[tagName] = fConstr;
},
setProcessingInstruction: function(target, fConstr) {
this.processingInstructions[target] = fConstr;
}
};
/**
* The parser of the Ajax.org Markup Language. Besides aml this parser takes care
* of distributing parsing tasks to other parsers like the native html parser and
* the xsd parser.
* @parser
* @private
*
* @define include element that loads another aml files.
* Example:
*
*
*
* @attribute {String} src the location of the aml file to include in this application.
*
*/
apf.aml = new apf.AmlNamespace();
apf.setNamespace("http://ajax.org/2005/aml", apf.aml);
apf.__AMLNODE__ = 1 << 14;
/**
* All elements inheriting from this {@link term.baseclass baseclass} have Document Object Model (DOM) support. The DOM
* is the primary method for accessing and manipulating an XML document. This
* includes HTML documents and AML documents. Every element in the ajax.org
* markup language can be manipulated using the W3C DOM. This means
* that every element and attribute you can set in the XML format, can be
* changed, set, removed, reparented, _e.t.c._ at runtime. This offers a great deal of
* flexibility.
*
* Well known methods
* from this specification are: `appendChild`, `removeChild`, `setAttribute`, and
* `insertBefore`--to name a few. The Ajax.org Platform aims to implement DOM1
* completely and parts of DOM2. For more information see {@link http://www.w3.org/DOM/}
* or {@link http://www.w3schools.com/dom/default.asp}.
*
* #### Example:
*
* Here's a basic window using the Ajax.org Markup Language (AML):
*
* ```xml
*
*
*
* ```
*
*
* Using the Document Object Model in JavaScript:
*
* ```javascript
* //The following line is only there for completeness sake. In fact apf
* //automatically adds a reference in javascript called winExample based
* //on the id it has.
* var winExample = apf.document.getElementById("winExample");
* winExample.setAttribute("title", "Example");
* winExample.setAttribute("icon", "icoFolder.gif");
* winExample.setAttribute("left", "100");
*
* var lblNew = apf.document.createElement("label");
* winExample.appendChild(lblNew);
* lblNew.setAttribute("caption", "Example");
*
* tstButton.setAttribute("caption", "Click me");
* ```
*
* That would be the same as having the following AML:
*
* ```xml
*
*
*
*
* ```
*
* #### Remarks
* Because the W3C DOM is native to all modern browsers the internet is full
* of tutorials and documentation for this API. If you need more information,
* it's a good idea to search for tutorials online.
*
* @class apf.AmlNode
* @baseclass
* @inherits apf.Class
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.5
*/
/**
* @event DOMNodeInserted Fires when a DOM node is inserted.
*/
/**
* @event DOMNodeInsertedIntoDocument Fires when a DOM node is inserted into the document.
*/
/**
* @event DOMNodeRemoved Fires when a DOM node is removed.
*/
/**
* @event DOMNodeRemovedFromDocument Fires when a DOM node is removed from a document.
*/
apf.AmlNode = function(){
this.$init(function(){
/**
* Nodelist containing all the child nodes of this element.
*/
this.childNodes = []; //@todo AmlNodeList
});
};
(function() {
/**
* Returns a string representation of this object.
* @returns A string defining the object.
*/
this.toString = function(){
if (this.nodeName)
return "[" + this.nodeName.uCaseFirst() + " Node]";
return "[" + this.localName.uCaseFirst() + " Element Node, <"
+ (this.prefix ? this.prefix + ":" : "") + this.localName + " "
+ this.attributes.join(" ")
+ " /> : " + (this.name || this.$uniqueId || "") + "]";
};
/**
* Number specifying the type of node within the document.
* @type {Number}
*/
this.$regbase = this.$regbase | apf.__AMLNODE__;
/**
* The constant for a DOM element node.
* @type {Number}
*/
this.NODE_ELEMENT = 1;
/**
* The constant for a DOM attribute node.
* @type {Number}
*/
this.NODE_ATTRIBUTE = 2;
/**
* The constant for a DOM text node.
* @type {Number}
*/
this.NODE_TEXT = 3;
/**
* The constant for a DOM cdata section node.
* @type {Number}
*/
this.NODE_CDATA_SECTION = 4;
/**
* The constant for a DOM entity reference node.
* @type {Number}
*/
this.NODE_ENTITY_REFERENCE = 5;
/**
* The constant for a DOM entity node.
* @type {Number}
*/
this.NODE_ENTITY = 6;
/**
* The constant for a DOM processing instruction node.
* @type {Number}
*/
this.NODE_PROCESSING_INSTRUCTION = 7;
/**
* The constant for a DOM comment node.
* @type {Number}
*/
this.NODE_COMMENT = 8;
/**
* The constant for a DOM document node.
* @type {Number}
*/
this.NODE_DOCUMENT = 9;
/**
* The constant for a DOM document type node.
* @type {Number}
*/
this.NODE_DOCUMENT_TYPE = 10;
/**
* The constant for a DOM document fragment node.
* @type {Number}
*/
this.NODE_DOCUMENT_FRAGMENT = 11;
/**
* The constant for a DOM notation node.
* @type {Number}
*/
this.NODE_NOTATION = 12;
/**
* The document node of this application
* @type {apf.AmlDocument}
*/
this.ownerDocument = null;
/**
* Returns the value of the current node.
* @type {apf.AmlNode}
*/
this.nodeValue = "";
/**
* The namespace URI of the node, or `null` if it is unspecified (read-only).
*
* When the node is a document, it returns the XML namespace for the current
* document.
* @type {String}
*/
this.namespaceURI = "";
/*
* @todo
*/
//this.baseURI = alsdjlasdj
/*
* @todo
*/
//this.prefix = asdkljahqsdkh
/**
*
* @inheritdoc apf.AmlNode.insertBefore
*
*/
this.appendChild =
/**
* Inserts an element before another element in the list of children of this
* element. If the element was already a child of another element it is
* removed from that parent before adding it this element.
*
* @method insertBefore
* @param {apf.AmlNode} amlNode The element to insert as child of this element.
* @param {apf.AmlNode} beforeNode The element which determines the insertion position of the element.
* @return {apf.AmlNode} The inserted node
*/
this.insertBefore = function(amlNode, beforeNode, noHtmlDomEdit) {
if (this.nodeType == this.NODE_DOCUMENT) {
if (this.childNodes.length) {
throw new Error(apf.formatErrorString(0, this,
"Insertbefore DOM operation",
"Only one top level element is allowed in an AML document."));
}
else this.documentElement = amlNode; //@todo apf3.0 removal
}
if (amlNode == beforeNode)
return amlNode;
if (this == amlNode) {
throw new Error(apf.formatErrorString(0, this,
"Insertbefore DOM operation",
"Cannot append node as a child of itself."));
}
if (amlNode.nodeType == this.NODE_DOCUMENT_FRAGMENT) {
var nodes = amlNode.childNodes.slice(0);
for (var i = 0, l = nodes.length; i < l; i++) {
this.insertBefore(nodes[i], beforeNode);
}
return amlNode;
}
var isMoveWithinParent = amlNode.parentNode == this,
oldParentHtmlNode = amlNode.$pHtmlNode,
oldParent = amlNode.parentNode,
index = -1,
_self = this;
if (beforeNode) {
index = this.childNodes.indexOf(beforeNode);
if (index < 0) {
return false;
}
}
if (!amlNode.ownerDocument)
amlNode.ownerDocument = this.ownerDocument || apf.ownerDocument;
if (amlNode.parentNode)
amlNode.removeNode(isMoveWithinParent, true);//noHtmlDomEdit);
amlNode.parentNode = this;
if (beforeNode)
index = this.childNodes.indexOf(beforeNode);
if (beforeNode) {
amlNode.nextSibling = beforeNode;
amlNode.previousSibling = beforeNode.previousSibling;
beforeNode.previousSibling = amlNode;
if (amlNode.previousSibling)
amlNode.previousSibling.nextSibling = amlNode;
}
if (index >= 0) {
this.childNodes = this.childNodes.slice(0, index).concat(amlNode,
this.childNodes.slice(index));
}
else {
index = this.childNodes.push(amlNode) - 1;
amlNode.nextSibling = null;
if (index > 0) {
amlNode.previousSibling = this.childNodes[index - 1];
amlNode.previousSibling.nextSibling = amlNode;
}
else {
amlNode.previousSibling = null;
}
}
this.firstChild = this.childNodes[0];
this.lastChild = this.childNodes[this.childNodes.length - 1];
//@todo fix event struture, fix tree events
var initialAppend = !amlNode.$amlLoaded;
function triggerUpdate(){
amlNode.$pHtmlNode = _self.canHaveChildren ? _self.$int : document.body;
//@todo this is a hack, a good solution should be found
if (document.adoptNode && amlNode.$ext && amlNode.$ext.nodeType == 1) {
var reappendlist = [];
var iframelist = apf.getArrayFromNodelist(
amlNode.$ext.getElementsByTagName("iframe"));
if (amlNode.$ext.tagName == "IFRAME")
document.adoptNode(amlNode.$ext);
for (var i = 0; i < iframelist.length; i++) {
reappendlist[i] = [
iframelist[i].parentNode,
iframelist[i].nextSibling,
document.adoptNode(iframelist[i]),
]
}
}
var nextNode = beforeNode;
if (!initialAppend && !noHtmlDomEdit && amlNode.$ext && !amlNode.$coreHtml) {
nextNode = beforeNode;
while (nextNode && !(nextNode.$altExt || nextNode.$ext)) {
nextNode = nextNode.nextSibling;
}
amlNode.$pHtmlNode.insertBefore(amlNode.$altExt || amlNode.$ext,
nextNode && (nextNode.$altExt || nextNode.$ext) || null);
for (var i = reappendlist.length - 1; i >= 0; i--) {
reappendlist[i][0].insertBefore(
reappendlist[i][2],
reappendlist[i][1]);
}
reappendlist = [];
}
//Signal node and all it's ancestors
amlNode.dispatchEvent("DOMNodeInserted", {
$beforeNode: beforeNode,
relatedNode: _self,
$isMoveWithinParent: isMoveWithinParent,
$oldParentHtmlNode: oldParentHtmlNode,
$oldParent: oldParent,
bubbles: true
});
if (initialAppend && !noHtmlDomEdit && beforeNode && amlNode.$ext && !amlNode.$coreHtml) {
nextNode = beforeNode;
while (nextNode && !(nextNode.$altExt || nextNode.$ext)) {
nextNode = nextNode.nextSibling;
}
amlNode.$pHtmlNode.insertBefore(amlNode.$altExt || amlNode.$ext,
nextNode && (nextNode.$altExt || nextNode.$ext) || null);
for (var i = reappendlist.length - 1; i >= 0; i--) {
reappendlist[i][0].insertBefore(
reappendlist[i][2],
reappendlist[i][1]);
}
}
}
var doc = this.nodeType == this.NODE_DOCUMENT ? this : this.ownerDocument;
if (!doc || doc.$domParser.$isPaused(this))
return amlNode;
// Don't update the tree if this is a doc fragment or if this element is not inited yet
if (this.nodeType == this.NODE_DOCUMENT_FRAGMENT || !this.$amlLoaded)
return amlNode;
//@todo review this...
if (initialAppend && !amlNode.render) { // && (nNodes = node.childNodes).length ??
(this.ownerDocument || this).$domParser.$continueParsing(amlNode, {delay: true});
}
triggerUpdate();
return amlNode;
};
/**
* Removes this element from the document hierarchy. Call-chaining is
* supported.
*
*/
this.removeNode = function(doOnlyAdmin, noHtmlDomEdit) {
if (!this.parentNode || !this.parentNode.childNodes)
return this;
this.parentNode.childNodes.remove(this);
//If we're not loaded yet, just remove us from the aml to be parsed
if (this.$amlLoaded && !apf.isDestroying) {
//this.parentNode.$aml.removeChild(this.$aml);
this.dispatchEvent("DOMNodeRemoved", {
relatedNode: this.parentNode,
bubbles: true,
$doOnlyAdmin: doOnlyAdmin
});
if (!noHtmlDomEdit && !doOnlyAdmin && this.$ext && this.$ext.parentNode) {
this.$ext.parentNode.removeChild(this.$ext);
//delete this.$ext; //WTF???
}
}
if (this.parentNode.firstChild == this)
this.parentNode.firstChild = this.nextSibling;
if (this.parentNode.lastChild == this)
this.parentNode.lastChild = this.previousSibling;
if (this.nextSibling)
this.nextSibling.previousSibling = this.previousSibling;
if (this.previousSibling)
this.previousSibling.nextSibling = this.nextSibling;
this.$pHtmlNode =
this.parentNode =
this.previousSibling =
this.nextSibling = null;
return this;
};
/**
* Removes a child from the node list of this element. Call-chaining is
* supported.
* @param {apf.AmlNode} childNode The child node to remove
*/
this.removeChild = function(childNode) {
childNode.removeNode();
return this;
};
//@todo
this.replaceChild = function(){};
/**
* Clones this element, creating an exact copy of it--but does not insert
* it in the document hierarchy.
*
* @param {Boolean} deep Specifies whether the elements are cloned recursively.
* @return {apf.AmlNode} The cloned element.
*/
this.cloneNode = function(deep) {
if (deep && this.nodeType == 1) {
return this.ownerDocument.$domParser.parseFromXml(this, {
doc: this.ownerDocument,
delay: true
}).childNodes[0];
}
else {
return this.ownerDocument.$domParser.$createNode(
this.ownerDocument, this.nodeType, this);
}
};
//@todo
this.canDispatch = function(namespaceURI, type) {};
//@todo
this.compareDocumentPosition = function(otherNode) {
/*
DOCUMENT_POSITION_DISCONNECTED = 0x01;
DOCUMENT_POSITION_PRECEDING = 0x02;
DOCUMENT_POSITION_FOLLOWING = 0x04;
DOCUMENT_POSITION_CONTAINS = 0x08;
DOCUMENT_POSITION_CONTAINED_BY = 0x10;
*/
};
this.hasAttributes = function(){
return this.attributes && this.attributes.length;
};
this.hasChildNodes = function(){
return this.childNodes && this.childNodes.length;
};
this.isDefaultNamespace = function(namespaceURI) {
if (node.nodeType == 1) {
if (!this.prefix)
return this.namespaceURI == namespaceURI;
//@todo Loop through attributes here
}
var node = this.parentNode || this.ownerElement;
return node && node.isDefaultNamespace(namespaceURI);
};
this.lookupNamespaceURI = function(prefix) {
if (node.nodeType == 1) {
if (this.namespaceURI && prefix == this.prefix)
return this.namespaceURI ;
//@todo Loop through attributes here
}
var node = this.parentNode || this.ownerElement;
return node && node.lookupNamespaceURI(prefix);
};
this.lookupPrefix = function(namespaceURI) {
if (this.nodeType == 1) {
if (namespaceURI == this.namespaceURI && this.prefix)
return this.prefix;
//@todo Loop through attributes here
}
var node = this.parentNode || this.ownerElement;
return node && node.lookupPrefix(namespaceURI);
};
this.normalize = function(){};
// *** Xpath support *** //
/**
* Queries the AML DOM using the W3C xPath query language and returns a node
* list. This is not an official API call, but can be useful in certain cases.
*
* @param {String} sExpr The xpath expression to query the AML DOM tree with.
* @param {apf.AmlNode} [contextNode] The element that serves as the starting point of the search. Defaults to this element.
* @returns {NodeList} List of found nodes.
*/
this.selectNodes = function(sExpr, contextNode) {
if (!apf) return;
if (!apf.XPath)
apf.runXpath();
return apf.XPath.selectNodes(sExpr,
contextNode || (this.nodeType == 9 ? this.documentElement : this));
};
/**
* Queries the AML dom using the W3C xPath query language and returns a single
* node. This is not an official API call, but can be useful in certain cases.
*
* @param {String} sExpr The xpath expression to query the AML DOM tree with.
* @param {apf.AmlNode} [contextNode] The element that serves as the starting point of the search. Defaults to this element.
* @returns {apf.AmlNode} The first node that matches the query.
*/
this.selectSingleNode = function(sExpr, contextNode) {
if (!apf) return;
if (!apf.XPath)
apf.runXpath();
return apf.XPath.selectNodes(sExpr,
contextNode || (this.nodeType == 9 ? this.documentElement : this))[0];
};
/*this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
}, true);*/
}).call(apf.AmlNode.prototype = new apf.Class());
/**
* Represents a single element within an AML node.
*
* @class apf.AmlElement
* @baseclass
* @inherits apf.AmlNode
*/
apf.AmlElement = function(struct, tagName) {
var $init = this.$init;
this.$init = function(tagName, nodeFunc, struct) {
this.$supportedProperties = this.$supportedProperties.slice();
var prop, p, q;
p = this.$propHandlers;
q = this.$propHandlers = {};
for (prop in p)
q[prop] = p[prop];
p = this.$booleanProperties;
q = this.$booleanProperties = {};
for (prop in p)
q[prop] = p[prop];
return $init.call(this, tagName, nodeFunc, struct);
};
this.$init(function(tagName, nodeFunc, struct) {
this.$events = {};
this.$inheritProperties = {};
/*
* A node list containing all the attributes. This is implemented according to the
* W3C specification.
*
* For more information, see [[apf.AmlElement.getAttribute]] and [[apf.AmlElement.setAttribute]].
*
* #### Example
*
* ```javascript
* for (var i = 0; i < obj.attributes.length; i++) {
* alert(obj.attributes.item(i));
* }
* ```
* @type {apf.AmlNamedNodeMap}
*/
this.attributes = new apf.AmlNamedNodeMap(this); //@todo apf3.0 move to init?
/**
* Defines the purpose of this element. Possible values include:
* - `apf.NODE_VISIBLE`: This element has a GUI representation
* - `apf.NODE_HIDDEN`: This element does not display a GUI
* @type {Number}
*/
this.nodeFunc = nodeFunc;
/**
* The local name of this element
* @type {String}
*/
this.localName = tagName; //@todo
//Parse struct to create attributes and child nodes
if (struct) {
var nodes, prop, i, l, attr;
if (struct.childNodes) {
nodes = struct.childNodes;
delete struct.childNodes; //why delete?
}
//Attributes
for (prop in struct) {
if (prop == "htmlNode") continue;
attr = new apf.AmlAttr(this, prop, struct[prop]);
//These exceptions should be generalized
if (prop == "id")
this.$propHandlers["id"].call(this, this.id = struct.id);
else if (prop == "hotkey")
this.$propHandlers["hotkey"].call(this, this.hotkey = struct.hotkey);
else if (prop.substr(0, 2) == "on")
attr.$triggerUpdate();
this.attributes.push(attr);
}
if (!this.ownerDocument) {
this.ownerDocument = apf.document;
this.prefix = "a";
this.namespaceURI = apf.ns.aml;
this.tagName = tagName;
}
if (nodes) {
this.childNodes = nodes;
for (i = 0, l = nodes.length; i < l; i++) {
nodes[i].nextSibling = nodes[i + 1] || null;
nodes[i].previousSibling = nodes[i - 1] || null;
nodes[i].parentNode = this;
}
this.firstChild = nodes[0] || null;
this.lastChild = nodes[nodes.length - 1] || null;
}
//Temp hack
this.$aml = apf.$emptyNode || (apf.$emptyNode = apf.getXml(""));
}
});
if (tagName) //of typeof is not function and not true
$init.call(this, tagName, apf.NODE_HIDDEN, struct);
};
(function(){
/**
* A number specifying the type of node within the document.
* @type {Number}
*/
this.nodeType = this.NODE_ELEMENT;
this.canHaveChildren = true;
this.$propHandlers = {
/**
* @attribute {String} id The identifier of this element. When set, this
* identifier is the name of the variable in JavaScript to access this
* element directly. This identifier is also the way to get a reference to
* this element using `apf.document.getElementById()`.
*
* #### Example
*
* ```xml
*
*
* alert(barExample);
*
* ```
*/
"id": function(value) {
if (this.name == value || !value)
return;
if (self[this.name] == this) {
self[this.name] = null;
apf.nameserver.remove(this.localName, this);
apf.nameserver.remove("all", this);
}
if (!self[value] || !self[value].hasFeature) {
try {
self[value] = this;
}
catch (ex) {
}
}
//@todo dispatch event for new name creation.
//@todo old name disposal
apf.nameserver.register(this.localName, value, this)
apf.nameserver.register("all", value, this)
this.name = value;
}
};
this.$booleanProperties = {};
this.$inheritProperties = {};
this.$supportedProperties = [];
/**
* Returns a list of elements with the given tag name.
*
* The subtree below the specified element is searched, excluding the
* element itself.
*
* @param {String} tagName The tag name to look for. The special string "*" represents any tag name.
* @param {Boolean} [norecur] If specified, defines whether or not to check recursively
* @return {NodeList} Contains any nodes matching the search string
*/
this.getElementsByTagName = function(tagName, norecur) {
tagName = tagName.toLowerCase();
var node, i, l,
nodes = this.childNodes,
result = [];
for (i = 0, l = nodes.length; i < l; i++) {
if ((node = nodes[i]).nodeType != 1)
continue;
if (node.tagName == tagName || tagName == "*")
result.push(node);
if (!norecur && node.nodeType == 1)
result = result.concat(node.getElementsByTagName(tagName));
}
return result;
};
/**
* Returns a list of elements with the given tag name and the specified namespace URI.
*
* The subtree below the specified element is searched, excluding the
* element itself.
*
* @param {String} namespaceURI The namespace URI name to look for.
* @param {String} localName The tag name to look for. The special string "*" represents any tag name.
* @param {Boolean} [norecur] If specified, defines whether or not to check recursively
* @return {NodeList} Contains any nodes matching the search string
*/
this.getElementsByTagNameNS = function(namespaceURI, localName, norecur) {
localName = localName.toLowerCase();
var node, i, l,
nodes = this.childNodes,
result = [];
for (i = 0, l = nodes.length; i < l; i++) {
if ((node = nodes[i]).nodeType != 1)
continue;
if (node.namespaceURI == namespaceURI && (node.localName == localName || localName == "*"))
result.push(node);
if (!norecur && !node.$amlDestroyed && node.nodeType == 1)
result = result.concat(node.getElementsByTagNameNS(namespaceURI, localName));
}
return result;
};
/**
* Sets an attribute on this element.
* @chainable
* @param {String} name The name of the attribute to which the value is set
* @param {String} value The new value of the attribute.
* @param {Boolean} [noTrigger] If specified, does not emit events
* [[apf.AmlNode@DOMNodeInsertedIntoDocument]] and [[apf.AmlNode@DOMNodeInserted]].
*/
this.setAttribute = function(name, value, noTrigger) {
name = name.toLowerCase();
var a = this.attributes.getNamedItem(name);
if (!a) {
this.attributes.push(a = new apf.AmlAttr(this, name, value));
if (!this.$amlLoaded && name != "id" && name != "hotkey")
return;
if (noTrigger)
a.$setValue(value);
else {
//@todo apf3.0 domattr
a.dispatchEvent("DOMNodeInsertedIntoDocument", {
relatedNode: this
});
//@todo apf3.0 domattr
a.dispatchEvent("DOMNodeInserted", {
relatedNode: this,
bubbles: true
});
}
return;
}
var oldValue = a.nodeValue;
a.$setValue(value);
if (noTrigger || !this.$amlLoaded)
return;
//@todo apf3.0 domattr
a.$triggerUpdate(null, oldValue);
};
//@todo apf3.0 domattr
this.setAttributeNode = function(attrNode) {
this.attributes.setNamedItem(attrNode);
};
this.setAttributeNS = function(namespaceURI, name, value) {
return this.setAttribute(name, value);
};
//@todo apf3.0 domattr
this.hasAttribute = function(name) {
return this.getAttributeNode(name) ? true : false;
};
//@todo
this.hasAttributeNS = function(namespaceURI, name) {
return this.hasAttribute(name);
};
/**
* Removes an attribute from this element.
* @chainable
* @param {String} name The name of the attribute to remove.
* @returns {apf.AmlElement} The modified element.
*/
this.removeAttribute = function(name){ //@todo apf3.0 domattr
this.attributes.removeNamedItem(name);
return this;
};
//@todo apf3.0 domattr
this.removeAttributeNS = function(namespaceURI, name) {
return this.removeAttribute(name);
};
//@todo apf3.0 domattr
this.removeAttributeNode = function(attrNode) {
this.attributes.removeNamedItem(attrNode.name); //@todo this should probably be slightly different.
};
/**
* Retrieves the value of an attribute of this element.
*
* @param {String} name The name of the attribute for which to return the value.
* @param {Boolean} [inherited] if specified, takes into consideration that the attribute is inherited
* @return {String} The value of the attribute, or `null` if none was found with the name specified.
*/
this.getAttribute = function(name, inherited) {
var item = this.attributes.getNamedItem(name);
return item ? (inherited
? item.inheritedValue || item.nodeValue
: item.nodeValue) : null;
};
/**
* Retrieves the attribute node for a given name
*
* @param {String} name The name of the attribute to find.
* @return {apf.AmlNode} The attribute node, or `null` if none was found with the name specified.
*/
this.getAttributeNode = function(name) {
return this.attributes.getNamedItem(name);
};
this.getBoundingClientRect = function(){
return new apf.AmlTextRectangle(this);
};
//@todo
this.querySelector = function(){
// here we should use: http://code.google.com/p/css2xpath/source/browse/trunk/src/css2xpath.js
};
//@todo
this.querySelectorAll = function(){
// here we should use: http://code.google.com/p/css2xpath/source/browse/trunk/src/css2xpath.js
};
//@todo
this.scrollIntoView = function(){
};
/**
* Replaces the child AML elements with new AML.
* @param {Mixed} amlDefNode The AML to be loaded. This can be a string or a parsed piece of XML.
* @param {HTMLElement} oInt The HTML parent of the created AML elements.
*/
this.replaceMarkup = function(amlDefNode, options) {
if (!options)
options = {};
if (!options.$intAML)
options.$intAML = this.$aml;
if (!options.$int)
options.$int = this.$int;
options.clear = true;
//Remove All the childNodes
for (var i = this.childNodes.length - 1; i >= 0; i--) {
var oItem = this.childNodes[i];
/*var nodes = oItem.childNodes;
for (var k = 0; k < nodes.length; k++)
if (nodes[k].destroy)
nodes[k].destroy(true);
if (oItem.$aml && oItem.$aml.parentNode)
oItem.$aml.parentNode.removeChild(oItem.$aml);*/
if (oItem.destroy)
oItem.destroy(true);
if (oItem.$ext != this.$int)
apf.destroyHtmlNode(oItem.$ext);
}
this.childNodes.length = 0;
if (options.noLoadingMsg !== false)
this.$int.innerHTML = "
loading...
";
//Do an insertMarkup
this.insertMarkup(amlDefNode, options);
};
/**
* Inserts new AML into this element.
* @param {Mixed} amlDefNode The AML to be loaded. This can be a string or a parsed piece of XML.
* @param {Object} options Additional options to pass. It can include the following properties:
* - callback ([[Function]]): A function to call once the insertion completes.
* - clear ([[Boolean]]): If set, the AML has the attribute "clear" attached to it
*/
this.insertMarkup = function(amlDefNode, options) {
var _self = this;
var include = new apf.XiInclude();
if (amlDefNode.trim().charAt(0) == "<")
amlDefNode = apf.getXml(amlDefNode);
include.setAttribute("href", amlDefNode);
if (options && options.clear)
include.setAttribute("clear", true);
include.options = options;
include.callback = function(){
_self.dispatchEvent("afteramlinserted", {src: amlDefNode});
options && options.callback && options.callback();
setTimeout(function(){
include.destroy(true, true);
});
};
this.appendChild(include);
};
//@todo prefix only needs on top element
this.serialize = function(shallow) {
if (shallow || !this.firstChild) {
return "<"
+ (this.prefix
? this.prefix + ":" + this.localName + " xmlns:"
+ this.prefix + "=\"" + this.namespaceURI + "\""
: this.localName) + (this.attributes && this.attributes.length ? " " : "")
+ (this.attributes && this.attributes.join(" ") || "")
+ "/>";
}
else {
var str = ["<"
+ (this.prefix
? this.prefix + ":" + this.localName + " xmlns:"
+ this.prefix + "=\"" + this.namespaceURI + "\""
: this.localName) + (this.attributes && this.attributes.length ? " " : "")
+ (this.attributes && this.attributes.join(" ") || "")
+ ">"];
for (var i = this.firstChild; i; i = i.nextSibling)
str.push(i.serialize());
return str.join("") + "" + (this.prefix ? this.prefix
+ ":" + this.localName : this.localName) + ">";
}
};
this.$setInheritedAttribute = function(prop) {
var value, node = this, isInherit = false;
value = node.getAttribute(prop);
if (!value) {
node = node.parentNode;
//Second argument fetches special inheritance value, if any
while (node && node.nodeType == 1 && !(value = node.getAttribute(prop, true))) {
node = node.parentNode;
}
isInherit = true;
}
if (!value && apf.config && prop)
value = apf.config[prop];
if (value) {
//Remove any bounds if relevant
this.$clearDynamicProperty(prop);
}
if (isInherit)
this.$inheritProperties[prop] = 2;
if (value) {
if (typeof value == "string"
&& (value.indexOf("{") > -1 || value.indexOf("[") > -1)) {
this.$setDynamicProperty(prop, value);
}
else
this.setProperty(prop, value, false, false, 2);
}
return value;
};
//@todo in proper W3C implementation this needs to change
//@todo this won't work with a combo of remove/append
this.addEventListener("DOMNodeInserted", function(e) {
if (e.currentTarget != this || e.$isMoveWithinParent || !e.$oldParent)
return;
//Check inherited attributes for reparenting
/*
States:
-1 Set
undefined Pass through
2 Inherited
3 Semi-inherited
10 Dynamic property
*/
var vOld, vNew;
var aci = apf.config.$inheritProperties;
for (var prop in aci) {
vOld = apf.getInheritedAttribute(e.$oldParent, prop);
vNew = apf.getInheritedAttribute(this.parentNode, prop);
//Property has changed, lets recursively set it on inherited nodes
if (vOld != vNew) {
//@todo code duplication from class.js
(function recur(nodes) {
var i, l, node, n;
for (i = 0, l = nodes.length; i < l; i++) {
node = nodes[i];
if (node.nodeType != 1 && node.nodeType != 7)
continue;
//Pass through
n = node.$inheritProperties[prop];
if (aci[prop] == 1 && !n)
recur(node.childNodes);
//Set inherited property
//@todo why are dynamic properties overwritten??
else if (!(n < 0)) {//Will also pass through undefined - but why??? @todo seems inefficient
if (n == 3) {
var sameValue = node[prop];
node[prop] = null;
}
node.setProperty(prop, n != 3
? vNew
: sameValue, false, false, n); //This is recursive already
}
}
})([this]);
}
}
});
this.$handlePropSet = function(prop, value, force) {
if (this.$booleanProperties[prop])
value = apf.isTrue(value);
this[prop] = value;
var handler;
return (handler = this.$propHandlers && this.$propHandlers[prop]
|| this.nodeFunc == apf.NODE_VISIBLE && apf.GuiElement && apf.GuiElement.propHandlers[prop] || null)
&& handler.call(this, value, prop, force);
};
//var aci = apf.config.$inheritProperties; << UNUSED
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var a, i, l, attr = this.attributes;
//Set all attributes
for (i = 0, l = attr.length; i < l; i++) {
attr[i].dispatchEvent("DOMNodeInsertedIntoDocument");
}
}, true);
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
this.$amlLoaded = true;
});
}).call(apf.AmlElement.prototype = new apf.AmlNode());
//@todo apf3.0 The functions seem to not set nodeValue...
apf.AmlCharacterData = function(){
this.data = "";
this.length = 0;
this.$init(true);
this.appendData = function(sValue) {
this.dispatchEvent("DOMCharacterDataModified", {
value: sValue
});
};
this.deleteData = function(nOffset, nCount) {
this.dispatchEvent("DOMCharacterDataModified", {
offset: nOffset,
count: nCount
});
};
this.insertData = function(nOffset, nCount) {
this.dispatchEvent("DOMCharacterDataModified", {
offset: nOffset,
count: nCount
});
};
this.replaceData = function(nOffset, nCount, sValue) {
this.dispatchEvent("DOMCharacterDataModified", {
offset: nOffset,
count: nCount,
value: sValue
});
};
this.substringData = function(nOffset, nCount) {};
}
apf.AmlCharacterData.prototype = new apf.AmlNode();
apf.AmlText = function(isPrototype) {
this.$init(isPrototype);
};
(function(){
this.nodeType = this.NODE_TEXT;
this.nodeName = "#text";
this.serialize = function(){
return apf.escapeXML(this.nodeValue);
};
//@todo think about using this.replaceData();
this.$setValue = function(value) {
//if (!this.$amlLoaded)
//return;
this.dispatchEvent("DOMCharacterDataModified", {
bubbles: true,
prevValue: this.nodeValue,
newValue: this.nodeValue = value
});
if (this.$amlLoaded && this.$ext)
this.$ext.nodeValue = value;
}
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var pHtmlNode;
if (!(pHtmlNode = this.parentNode.$int) || this.parentNode.hasFeature(apf.__CHILDVALUE__))
return;
this.$amlLoaded = true;
var nodeValue = this.nodeValue;
//@todo optimize for inside elements?
if (apf.config.liveText && !this.parentNode.hasFeature(apf.__CHILDVALUE__)
&& (nodeValue.indexOf("{") > -1 || nodeValue.indexOf("[") > -1)) {
//Convert to live markup pi
this.$supportedProperties = [];
this.$propHandlers = {};
this.$booleanProperties = {};
this.$inheritProperties = {};
this.$propHandlers["calcdata"] = apf.LiveMarkupPi.prototype.$propHandlers["calcdata"];
this.$setInheritedAttribute = apf.AmlElement.prototype.$setInheritedAttribute;
this.implement(apf.StandardBinding);
pHtmlNode.appendChild(this.$ext = document.createElement("span"));
this.$setDynamicProperty("calcdata", this.nodeValue);
return;
}
if (apf.hasTextNodeWhiteSpaceBug) {
var nodeValue = nodeValue.replace(/[\t\n\r ]+/g, " ");
if (nodeValue && nodeValue != " ")
this.$ext = pHtmlNode.appendChild(
pHtmlNode.ownerDocument.createTextNode(nodeValue));
}
else
this.$ext = pHtmlNode.appendChild(
pHtmlNode.ownerDocument.createTextNode(nodeValue));
}, true);
}).call(apf.AmlText.prototype = new apf.AmlCharacterData());
apf.AmlAttr = function(ownerElement, name, value) {
this.$init();
if (ownerElement) {
this.ownerElement = ownerElement;
this.ownerDocument = ownerElement.ownerDocument;
}
this.nodeName = this.name = name;
this.nodeValue = this.value = value;
};
(function(){
this.nodeType = this.NODE_ATTRIBUTE;
this.MODIFICATION = 1;
this.ADDITION = 2;
this.REMOVAL = 3;
this.serialize =
this.toString = function(){
return this.name + "=\"" + apf.escapeXML(String(this.value)) + "\"";
};
this.$setValue = function(value) {
this.nodeValue = this.value = value;
this.specified = true;
//@todo apf3.0 domattr
this.ownerElement.dispatchEvent("DOMAttrModified", {
relatedNode: this,
attrChange: this.MODIFICATION,
attrName: this.name,
newValue: value,
prevValue: this.$lastValue || "",
bubbles: true
});
this.$lastValue = value;
};
this.$triggerUpdate = function(e, oldValue) {
var name = this.name,
value = this.value || this.nodeValue,
host = this.ownerElement,
isEvent = name.substr(0, 2) == "on";
if (!this.specified) {
//@todo This should be generalized
if (isEvent && this.$lastValue == value
|| name == "id" && host.id) {
this.specified = true;
return;
}
}
if (isEvent) {
if (host.$events[name])
host.removeEventListener(name.substr(2), host.$events[name]);
if (value)
host.addEventListener(name, (host.$events[name] =
(typeof value == "string"
?
new Function('event', value)
: value)));
return;
}
if (this.specified)
host.$clearDynamicProperty(name);
if (host.localName != "page" && typeof value == "string" && (host.$attrExcludePropBind[name] ||
(value.indexOf("{") > -1 || value.indexOf("[") > -1)))
host.$setDynamicProperty(name, value);
else
{
host.setProperty(name, value); //@todo apf3.0 is this a lot slower?
}
//host.$handlePropSet(name, value);
if (this.specified) {
//@todo apf3.0 domattr - slow?
host.dispatchEvent("DOMAttrModified", { //@todo this is not good, node might not be specified at init
relatedNode: this,
attrChange: this.MODIFICATION,
attrName: name,
newValue: value,
prevValue: this.$lastValue || "",
bubbles: true
});
}
else this.specified = true;
this.$lastValue = value;
};
//@todo apf3.0 domattr
this.addEventListener("DOMNodeInsertedIntoDocument", this.$triggerUpdate);
}).call(apf.AmlAttr.prototype = new apf.AmlNode());
apf.AmlCDATASection = function(isPrototype) {
this.nodeType = this.NODE_CDATA_SECTION;
this.nodeName = "#cdata-section";
this.$init(isPrototype);
};
apf.AmlCDATASection.prototype = new apf.AmlText(true);
apf.AmlCDATASection.prototype.serialize = function(){
return "";
};
apf.AmlComment = function(isPrototype) {
this.nodeType = this.NODE_COMMENT;
this.nodeName = "#comment";
this.$init(isPrototype);
};
(function(){
this.serialize = function(){
return "";
};
this.$setValue = function(value) {
this.dispatchEvent("DOMCharacterDataModified", {
bubbles: true,
newValue: value,
prevValue: this.nodeValue
});
}
}).call(apf.AmlComment.prototype = new apf.AmlCharacterData());
apf.AmlConfiguration = function(isPrototype) {
this.parameterNames = [];
this.$init(isPrototype);
};
(function(){
this.setParameter = this.setProperty;
this.getParameter = this.getProperty;
this.canSetParameter = function(name, value){ //@todo for value
return this.parameterNames.indexOf(name) > -1;
};
}).call(apf.AmlConfiguration.prototype = new apf.Class());
/**
* The AML document. This is the root of the DOM tree and has a nodeType with
* value 9 (`apf.NODE_DOCUMENT`).
*
* @class apf.AmlDocument
* @inherits apf.AmlNode
* @inherits apf.Class
* @default_private
* @see apf.AmlDom
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*/
apf.AmlDocument = function(){
this.$prefixes = {};
this.$namespaceURIs = {};
this.domConfig = new apf.AmlConfiguration();
this.$init();
};
(function() {
/**
* The type of node within the document.
* @type {Number}
*/
this.nodeType = this.NODE_DOCUMENT;
this.nodeFunc = apf.NODE_HIDDEN;
this.nodeName = "#document";
this.$amlLoaded = true;
this.activeElement = null; //@todo alias of window.foccussed;
this.doctype = null;
this.domConfig = null;
this.implementation = null;
this.characterSet = apf.characterSet;
/**
* The root element node of the AML application. This is an element with
* the tagName `'application'`. This is similar to the `'html'` element for regular HTML.
* @type {apf.AmlNode}
*/
this.documentElement = null;
/**
* Gets a AML element based on its id.
* @param {String} id The id of the AML element to return.
* @return {apf.AmlElement} The AML element with the id specified.
*/
this.getElementById = function(id) {
return self[id];
};
/**
* Returns a list of elements with the given tag name.
*
* The subtree below the [[apf.AmlDocument.documentElement]] is searched, excluding the
* element itself.
*
* @param {String} tagName The tag name to look for. The special string "*" represents any tag name.
* @return {NodeList} Contains any nodes matching the search string
*/
this.getElementsByTagName = function(tagName) {
var docEl, res = (docEl = this.documentElement)
.getElementsByTagName(tagName);
if (tagName == "*" || docEl.tagName == tagName)
res.unshift(docEl);
return res;
};
/**
* Returns a list of elements with the given tag name and the specified namespace URI.
*
* The subtree below the [[apf.AmlDocument.documentElement]] is searched, excluding the
* element itself.
*
* @param {String} namespaceURI The namespace URI name to look for.
* @param {String} tagName The tag name to look for. The special string "*" represents any tag name.
* @return {NodeList} Contains any nodes matching the search string
*/
this.getElementsByTagNameNS = function(nameSpaceURI, tagName) {
var docEl,
res = (docEl = this.documentElement)
.getElementsByTagNameNS(nameSpaceURI, tagName);
if (tagName == "*" || docEl.tagName == tagName && docEl.namespaceURI == nameSpaceURI)
res.unshift(docEl);
return res;
};
/**
* Creates a new AML element.
*
* @param {Mixed} qualifiedName Information about the new node to create. Possible values include:
* - [[String]]: The tag name of the new element to create
* - [[String]]: The AML definition for a single or multiple elemnts
* - [[XMLElement]]: The AML definition for a single or multiple elements
* @return {apf.AmlElement} The created AML element
*/
this.createElement = function(qualifiedName) {
return this.$domParser.$createNode(this, this.NODE_ELEMENT, null,
this.namespaceURI, qualifiedName);
};
/**
* Creates a new AML element within the given namespace.
*
* @param {String} namespaceURI The namespace URI name to use
* @param {Mixed} qualifiedName Information about the new node to create. Possible values include:
* - [[String]]: The tag name of the new element to create
* - [[String]]: The AML definition for a single or multiple elemnts
* - [[XMLElement]]: The AML definition for a single or multiple elements
* @return {apf.AmlElement} The created AML element
*/
this.createElementNS = function(namespaceURI, qualifiedName) {
return this.$domParser.$createNode(this, this.NODE_ELEMENT, null,
namespaceURI, qualifiedName);
};
/**
* Creates a copy of a node from an external document that can be inserted into the current document.
*
* @param {apf.AmlNode} node The node to import and copy
* @param {Boolean} [deep] Indicates whether the descendants of the imported node should also be imported
* @return {apf.AmlNode} The imported node
*/
this.importNode = function(node, deep) {
if (deep && node.nodeType == 1) {
return this.$domParser.parseFromXml(node, {
doc: this,
delay: true
}).childNodes[0];
}
else {
return this.$domParser.$createNode(this, node.nodeType, node);
}
};
/**
* Creates and returns a new attribute node.
*
* @param {String} nodeName The name of the attribute
* @return {apf.AmlNode} The attribute node
*/
this.createAttribute = function(nodeName) {
return this.$domParser.$createNode(this, this.NODE_ATTRIBUTE, null,
this.nameSpaceURI, nodeName);
};
/**
* Creates and returns a new attribute node, within a specified URI.
*
* @param {String} nameSpaceURI The name of the URI
* @param {String} nodeName The name of the attribute
* @return {apf.AmlNode} The attribute node
*/
this.createAttributeNS = function(nameSpaceURI, nodeName) {
return this.$domParser.$createNode(this, this.NODE_ATTRIBUTE, null,
nameSpaceURI, nodeName);
};
/**
* Creates and returns a new [[apf.AmlEvent]] .
*/
this.createEvent = function(){
return new apf.AmlEvent();
};
/**
* Creates and returns a new comment node.
* @param {String} nodeValue The data to be added to the comment
* @return {apf.AmlNode} The comment node
*/
this.createComment = function(nodeValue) {
return this.$domParser.$createNode(this, this.NODE_COMMENT, null, null,
null, nodeValue);
};
/**
* Creates and returns a new processing instruction node.
* @param {String} target The target part of the processing instruction node, like ``
* @param {String} data The data to be added to the PI
* @return {apf.AmlNode} The processing instruction node
*/
this.createProcessingInstruction = function(target, data) {
return this.$domParser.$createNode(this, this.NODE_PROCESSING_INSTRUCTION,
null, null, target, data);
};
/**
* Creates and returns a new CDATA section node.
* @param {String} nodeValue The data to be added to the CDATA node
* @return {apf.AmlNode} The CDATA section node
*/
this.createCDATASection = function(nodeValue) {
return this.$domParser.$createNode(this, this.NODE_CDATA_SECTION, null,
null, null, nodeValue);
};
/**
* Creates and returns a new Text node.
* @param {String} nodeValue The data to be added to the text node
* @return {apf.AmlNode} The Text node
*/
this.createTextNode = function(nodeValue) {
return this.$domParser.$createNode(this, this.NODE_TEXT, null, null,
null, nodeValue);
};
/**
* Creates and returns a new document fragment.
*/
this.createDocumentFragment = function(){
return this.$domParser.$createNode(this, this.NODE_DOCUMENT_FRAGMENT);
};
// @todo
this.querySelector = function(){};
// @todo
this.querySelectorAll = function(){};
// @todo
this.hasFocus = function(){
}
}).call(apf.AmlDocument.prototype = new apf.AmlNode());
apf.AmlDocumentFragment = function(isPrototype) {
this.$init(isPrototype);
};
apf.AmlDocumentFragment.prototype = new apf.AmlNode();
apf.AmlDocumentFragment.prototype.nodeName = "#document-fragment";
apf.AmlDocumentFragment.prototype.nodeType =
apf.AmlDocumentFragment.prototype.NODE_DOCUMENT_FRAGMENT;
/**
* Implementation of the W3C event object. An instance of this class is passed as
* the first argument of any event handler. As per event, it contains different
* properties giving context based information about the event.
* @class apf.AmlEvent
* @default_private
*/
apf.AmlEvent = function(name, data) {
this.name = name;
var prop;
for (prop in data)
this[prop] = data[prop];
};
apf.AmlEvent.prototype = {
bubbles: false,
cancelBubble: false,
/**
* Cancels the event (if it is cancelable), without stopping further
* propagation of the event.
*/
preventDefault: function(){
this.returnValue = false;
},
/**
* Prevents further propagation of the current event.
*/
stopPropagation: function(){
this.cancelBubble = true;
},
stop: function() {
this.returnValue = false;
this.cancelBubble = true;
}
};
//@todo apf3.0
apf.AmlNamedNodeMap = function(host) {
this.$host = host;
};
(function(){
this.getNamedItem = function(name) {
for (var i = 0; i < this.length; i++) {
if (this[i].name == name)
return this[i];
}
return false;
};
this.setNamedItem = function(node) {
var name = node.name;
for (var item, i = this.length - 1; i >= 0; i--) {
if (this[i].name == name) {
this[i].ownerElement = null;
this.splice(i, 1);
break;
}
}
this.push(node);
node.ownerElement = this.$host;
node.ownerDocument = this.$host.ownerDocument;
node.$triggerUpdate();
};
//@todo apf3.0 domattr
this.removeNamedItem = function(name) {
//Should deconstruct dynamic properties
for (var item, i = this.length - 1; i >= 0; i--) {
if (this[i].name == name) {
item = this[i];
this.splice(i, 1);
break;
}
}
if (!item) return false;
//@todo hack!
//this should be done properly
var oldValue = item.nodeValue;
item.nodeValue = item.value = "";
item.$triggerUpdate(null, oldValue);
item.ownerElement = null;
item.nodeValue = item.value = oldValue;
return item;
};
this.item = function(i) {
return this[i];
};
//if (apf.isIE < 8) { //Only supported by IE8 and above
this.length = 0;
this.splice = function(pos, length) {
for (var i = pos, l = this.length - length; i < l; i++) {
this[i] = this[i + 1];
}
delete this[i];
this.length -= length;
}
this.push = function(o) {
this[this.length++] = o;
return this.length;
}
//}
this.join = function(glue) {
var x = [];
for (var e, a, i = 0, l = this.length; i < l; i++) {
if ((e = (a = this[i]).ownerElement) && e.$inheritProperties[a.nodeName] != 2)
x.push(this[i]);
}
return x.join(glue);
}
}).call(apf.AmlNamedNodeMap.prototype = {}); //apf.isIE < 8 ? {} : []
apf.AmlProcessingInstruction = function(isPrototype) {
this.$init(isPrototype);
};
(function(){
this.nodeType = this.NODE_PROCESSING_INSTRUCTION;
/*
* @todo docs
*/
this.data = null;
/*
* @todo docs
*/
this.target = null;
this.serialize = function(){
return "" + this.target + "\n" + apf.escapeXML(this.nodeValue) + "\n?>";
};
this.reload = function(){
this.$handlePropSet("data", this.data);
};
//1 = force no bind rule, 2 = force bind rule
this.$attrExcludePropBind = apf.extend({
calcdata: 0 //Start in code mode
}, this.$attrExcludePropBind);
this.getAttribute = function(){};
this.$setInheritedAttribute = apf.AmlElement.prototype.$setInheritedAttribute;
this.$supportedProperties = [];
this.$propHandlers = {};
this.$booleanProperties = {};
this.$inheritProperties = {};
this.$setValue = function(value) {
this.setProperty("data", value);
};
this.$handlePropSet = function(prop, value, force) {
this[prop] = value;
if (prop == "data") {
this.$clearDynamicProperty("calcdata");
this.$setDynamicProperty("calcdata", value);
}
else if (prop == "target") {
//not implemented
}
else if (this.$propHandlers[prop]) {
this.$propHandlers[prop].call(this, value, prop);
}
};
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var pHtmlNode = e.pHtmlNode;
if (!pHtmlNode && (this.parentNode.$bindingRule
|| !(pHtmlNode = this.parentNode.$int)))
return;
pHtmlNode.appendChild(this.$ext = document.createElement("span"));
this.$ext.host = this;
this.$setDynamicProperty("calcdata", this.data);
}, true);
/*this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
this.$clearDynamicProperty("calcdata");
});*/
this.$destroy = function(){
this.$clearDynamicProperty("calcdata");
this.$propHandlers["calcdata"].call(this, "");
};
}).call(apf.AmlProcessingInstruction.prototype = new apf.AmlNode());
apf.AmlTextRectangle = function(host) {
var _self = this;
function handler(){
var pos = _self.getAbsolutePosition(_self.$ext);
_self.setProperty("left", pos[0]);
_self.setProperty("top", pos[1]);
_self.setProperty("right", document.documentElement.offsetWidth - pos[0]);
_self.setProperty("bottom", document.documentElement.offsetWidth - pos[1]);
}
host.addEventListener("prop.width", handler);
host.addEventListener("prop.height", handler);
host.addEventListener("prop.left", handler);
host.addEventListener("prop.top", handler);
handler.call(host);
};
apf.AmlTextRectangle.prototype = new apf.Class();
/*
* An object creating the XHTML namespace for the aml parser.
*
* @constructor
* @parser
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*/
apf.xhtml = new apf.AmlNamespace();
apf.setNamespace("http://www.w3.org/1999/xhtml", apf.xhtml);
/*
if (apf.getTextNode(x)) {
var data = {
amlNode: x,
htmlNode: o
}
}
*/
apf.XhtmlElement = function(struct, tagName) {
this.$init(tagName || true, apf.NODE_VISIBLE, struct);
this.$xoe = this.addEventListener;
this.addEventListener = this.$xae;
this.removeEventListener = this.$xre;
var _self = this;
this.$de = function(e) {
_self.dispatchEvent(e.type, null, e);
}
};
(function(){
var excludedEvents = {
"contextmenu": 1,
"keydown": 1,
"keypress": 1,
"keyup": 1,
"DOMNodeInserted": 2,
"DOMNodeInsertedIntoDocument": 2,
"DOMNodeRemoved": 2,
"DOMNodeRemovedFromDocument": 2
};
this.$xae = function(type, fn) {
this.$xoe.apply(this, arguments);
if (excludedEvents[type] > (this.editable ? 0 : 1)
|| type.substr(0, 5) == "prop.")
return;
if (this.$ext) {
if (type.substr(0,2) == "on")
type = type.substr(2);
apf.addListener(this.$ext, type, this.$de);
}
};
this.$xre = function(type, fn) {
apf.AmlElement.prototype.removeEventListener.apply(this, arguments);
if (this.$ext)
apf.removeListener(this.$ext, type, this.$de);
}
this.$handlePropSet = function(name, value, force, inherit) {
if (this.$booleanProperties[name])
value = apf.isTrue(value);
this[name] = value;
var handler = this.$propHandlers && this.$propHandlers[name]
|| apf.GuiElement.propHandlers[name];
if (handler)
handler.call(this, value, null, name);
else if (this.$int && (force || this.$amlLoaded)) {
this.$int.setAttribute(name, value);
}
};
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var pHtmlNode;
if (!(pHtmlNode = this.$pHtmlNode = this.parentNode.$int))
return;
var str, aml = this.$aml;
if (aml) {
if (aml.serialize)
str = aml.serialize();
else {
aml = aml.cloneNode(false);
str = aml.xml || aml.nodeValue;
}
str = str.replace(/ on\w+="[^"]*"| on\w+='[^']*'/g, "");
this.$ext =
this.$int = apf.insertHtmlNode(null, pHtmlNode, null, apf.html_entity_decode(str));
}
else {
this.$ext = this.$int =
pHtmlNode.appendChild(document.createElement(this.localName));
}
if (this.localName != "a")
this.$ext.host = this;
this.style = this.$ext.style;
}, true);
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
this.$amlLoaded = true;
if (this.$setLayout)
this.$setLayout();
});
}).call(apf.XhtmlElement.prototype = new apf.AmlElement());
apf.Init.addConditional(function(){
if (apf.isO3) return;
var prot = apf.XhtmlElement.prototype;
//prot.implement(apf.Interactive);
prot.implement(
apf.Anchoring
);
prot.$drawn = true;
prot.$setLayout = apf.GuiElement.prototype.$setLayout;
prot.addEventListener("DOMNodeInserted", function(e) {
if (e.currentTarget == this
&& "vbox|hbox|table".indexOf(this.parentNode.localName) == -1) {
this.$setLayout();
}
});
}, null, ["interactive"]);
apf.xhtml.setElement("@default", apf.XhtmlElement);
apf.XhtmlBodyElement = function(struct, tagName) {
this.$init(tagName || "body", apf.NODE_VISIBLE, struct);
};
(function(){
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
if (!this.ownerDocument.body)
this.ownerDocument.body = this;
this.$ext =
this.$int = document.body;
}, true);
}).call(apf.XhtmlBodyElement.prototype = new apf.AmlElement());
apf.Init.addConditional(function(){
if (apf.isO3) return;
var prot = apf.XhtmlBodyElement.prototype;
}, null, ["interactive"]);
apf.xhtml.setElement("body", apf.XhtmlBodyElement);
/**
* @todo description
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*/
apf.XhtmlHtmlElement = function(struct, tagName) {
this.$init(tagName || "html", apf.NODE_VISIBLE, struct);
this.$ext = document.documentElement;
this.$ext.host = this;
this.$int = document.body;
this.$tabList = []; //Prevents documentElement from being focussed
this.$focussable = apf.KEYBOARD;
this.focussable = true;
this.visible = true;
this.$isWindowContainer = true;
//this.focus = function(){ this.dispatchEvent("focus"); };
//this.blur = function(){ this.dispatchEvent("blur"); };
this.implement(apf.Focussable);
apf.window.$addFocus(this);
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var i, l, n, a, c,
attr = this.attributes, doc = this.ownerDocument;
for (i = 0, l = attr.length; i < l; i++) {
n = (a = attr[i]).nodeName.split(":");
if (n[0] == "xmlns") {
if (c = n[1]) {
doc.$prefixes[c] = a.nodeValue;
doc.$namespaceURIs[a.nodeValue] = c;
}
else {
doc.namespaceURI = a.nodeValue;
}
}
}
if (!doc.namespaceURI)
doc.namespaceURI = apf.ns.xhtml;
});
};
apf.XhtmlHtmlElement.prototype = new apf.XhtmlElement();
apf.xhtml.setElement("html", apf.XhtmlHtmlElement);
apf.XhtmlIgnoreElement = function(struct, tagName) {
this.$init(tagName, apf.NODE_VISIBLE, struct);
};
apf.XhtmlIgnoreElement.prototype = new apf.AmlElement();
apf.xhtml.setElement("script", apf.XhtmlIgnoreElement);
apf.xhtml.setElement("noscript", apf.XhtmlIgnoreElement);
apf.xhtml.setElement("head", apf.XhtmlIgnoreElement);
apf.xhtml.setElement("meta", apf.XhtmlIgnoreElement);
apf.XhtmlInputElement = function(struct, tagName) {
this.$init(tagName || "input", apf.NODE_VISIBLE, struct);
};
(function(){
this.$xae = apf.XhtmlElement.prototype.$xae;
this.$xre = apf.XhtmlElement.prototype.$xre;
this.$handlePropSet = function(name, value, force) {
if (name == "type")
return;
return apf.XhtmlElement.prototype.$handlePropSet.call(this, name, value, force);
};
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var pHtmlNode;
if (!(pHtmlNode = this.parentNode.$int))
return;
if (this.$aml) {
this.$ext =
this.$int = apf.insertHtmlNode(this.$aml.serialize
? this.$aml
: this.$aml.cloneNode(false), pHtmlNode);
}
else {
this.$ext = this.$int = document.createElement(this.localName);
if (this.getAttribute("type"))
this.$int.setAttribute("type", this.getAttribute("type"));
pHtmlNode.appendChild(this.$int);
}
}, true);
}).call(apf.XhtmlInputElement.prototype = new apf.AmlElement());
apf.xhtml.setElement("input", apf.XhtmlInputElement);
apf.XhtmlOptionElement = function(struct, tagName) {
this.$init(tagName || "option", apf.NODE_VISIBLE, struct);
};
(function(){
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
this.$ext =
this.$int = this.parentNode.$int.appendChild(
this.parentNode.$int.ownerDocument.createElement("option"));
if (this.value)
this.$int.setAttribute("value", this.value);
}, true);
}).call(apf.XhtmlOptionElement.prototype = new apf.AmlElement());
apf.xhtml.setElement("option", apf.XhtmlOptionElement);
apf.XhtmlSkipChildrenElement = function(struct, tagName) {
this.$init(tagName, apf.NODE_VISIBLE, struct);
};
(function(){
this.canHaveChildren = false;
this.$redraw = function(){
var _self = this;
apf.queue.add("redraw" + this.$uniqueId, function(){
var pHtmlNode = _self.$ext.parentNode;
var beforeNode = _self.$ext.nextSibling;
pHtmlNode.removeChild(_self.$ext);
_self.$ext = apf.insertHtmlNode(null, pHtmlNode, beforeNode, _self.$aml
? (_self.$aml.serialize ? _self.$aml.serialize() : _self.$aml.xml)
: _self.serialize());
});
}
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var pHtmlNode;
if (!(pHtmlNode = this.parentNode.$int))
return;
this.$ext = apf.insertHtmlNode(null, pHtmlNode, null, this.$aml
? (this.$aml.serialize ? this.$aml.serialize() : this.$aml.xml)
: this.serialize());
}, true);
}).call(apf.XhtmlSkipChildrenElement.prototype = new apf.AmlElement());
apf.xhtml.setElement("object", apf.XhtmlSkipChildrenElement);
apf.xhtml.setElement("embed", apf.XhtmlSkipChildrenElement);
apf.xhtml.setElement("table", apf.XhtmlSkipChildrenElement);
apf.xhtml.setElement("pre", apf.XhtmlSkipChildrenElement);
//XForms
/**
* Object creating the XML Include namespace for the aml parser.
*
* @constructor
* @parser
*
* @allownode simpleType, complexType
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*/
apf.xinclude = new apf.AmlNamespace();
apf.setNamespace("http://www.w3.org/2001/XInclude", apf.xinclude);
/**
* Defines a list of acceptable values
*/
apf.XiInclude = function(struct, tagName) {
this.$init(tagName || "include", apf.NODE_HIDDEN, struct);
};
apf.xinclude.setElement("include", apf.XiInclude);
apf.aml.setElement("include", apf.XiInclude);
//@todo test defer="true" situation
(function(){
this.$parsePrio = "002";
//1 = force no bind rule, 2 = force bind rule
/*this.$attrExcludePropBind = apf.extend({
href: 1,
src: 1
}, this.$attrExcludePropBind);*/
this.$propHandlers["href"] =
this.$propHandlers["src"] = function(value) {
if (typeof value != "string")
return finish.call(this, value);
if (value.trim().charAt(0) == "<") {
loadIncludeFile.call(this, value.trim());
return;
}
this.$path = value.charAt(0) == "{" //@todo this shouldn't happen anymore
? value
: apf.getAbsolutePath(apf.hostPath, value);
var domParser = this.ownerDocument.$domParser;
if (!this.defer) {
domParser.$pauseParsing.apply(domParser,
this.$parseContext = domParser.$parseContext || [this.parentNode]);
}
//var basePath = apf.hostPath;//only for recursion: apf.getDirname(xmlNode.getAttribute("filename")) ||
loadIncludeFile.call(this, this.$path);
};
function done(xmlNode) {
var addedNode = this.previousSibling || this.nextSibling;
if (this.callback) {
this.callback({
xmlNode: xmlNode,
amlNode: this.parentNode,
addedNode: addedNode
})
}
addedNode.dispatchEvent("DOMNodeInserted", {
$beforeNode: addedNode.nextSibling,
relatedNode: this.parentNode,
$isMoveWithinParent: false,
bubbles: true
});
//@todo hack!! this should never happen. Find out why it happens
if (this.parentNode)
this.parentNode.removeChild(this);
}
function finish(xmlNode) {
var domParser = this.ownerDocument.$domParser;
if (this.clear)
this.parentNode.$int.innerHTML = "";
if (xmlNode) {
domParser.parseFromXml(xmlNode, {
doc: this.ownerDocument,
amlNode: this.parentNode,
beforeNode: this,
include: true
});
if (!this.defer && this.$parseContext) {
var o = (this.$parseContext[1] || (this.$parseContext[1] = {})),
cb = o.callback,
_self = this;
o.callback = function(){
done.call(_self, xmlNode);
if (cb)
cb.call(_self.ownerDocument);
};
//@todo this is wrong... probably based on load order of last include element. Please rearchitect parse continuation.
if (domParser.$continueParsing(this.$parseContext[0]) === false) {
var o2 = (domParser.$parseContext[1] || (domParser.$parseContext[1] = {})),
cb2 = o.callback;
o2.callback = function(){
if (cb)
cb.call(_self.ownerDocument);
domParser.$continueParsing(_self.$parseContext[0]);
};
}
}
else
done.call(this, xmlNode);
}
else {
if (!this.defer)
domParser.$continueParsing(this.$parseContext[0]);
done.call(this, xmlNode);
}
}
function loadIncludeFile(path) {
var _self = this;
apf.getData(path, apf.extend(this.options || {}, {
callback: function(xmlString, state, extra) {
if (state != apf.SUCCESS) {
var oError = new Error(apf.formatErrorString(1007,
_self, "Loading Includes", "Could not load Include file '"
+ (path || _self.src)
+ "'\nReason: " + extra.message));
if (extra.tpModule.retryTimeout(extra, state, null, oError) === true)
return true;
apf.console.error(oError.message);
finish.call(_self, null);
//throw oError;
return;
}
//@todo apf3.0 please make one way of doing this
xmlString = xmlString.replace(/\<\!DOCTYPE[^>]*>/, "")
.replace(/^[\r\n\s]*/, ""); //.replace(/ /g, " ")
if (!apf.supportNamespaces)
xmlString = xmlString.replace(/xmlns\=\"[^"]*\"/g, "");
if (xmlString.indexOf(""
+ xmlString + "";
var xmlNode = apf.getXml(xmlString, null, true);//apf.getAmlDocFromString(xmlString);
if (!xmlNode) {
throw new Error(apf.formatErrorString(0, null,
"Loading include",
"Could not parse include file. Maybe the file does not exist?", xmlNode));
}
xmlNode.setAttribute("filename", extra.url);
finish.call(_self, xmlNode); //@todo add recursive includes support here
},
async: true,
ignoreOffline: true
}));
}
}).call(apf.XiInclude.prototype = new apf.AmlElement());
apf.__LIVEEDIT__ = 1 << 23;
apf.__ANCHORING__ = 1 << 13;
/**
* All elements inheriting from this {@link term.baseclass baseclass} have anchoring features. Each side of the
* element can be attached at a certain distance to its parent's rectangle.
*
* When the parent is resized, the anchored side of the element stays
* at the specified distance at all times. If both sides are anchored, the
* element size is changed to make sure the specified distance is maintained.
*
* #### Example
*
* This example shows a bar that has a 10% margin around it, and contains a
* frame that is displayed using different calculations and settings.
*
* ```xml
*
*
*
* ```
*
* ### Remarks
*
* This is one of three positioning methods. The other two are Alignment and Grid.
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.3
* @baseclass
* @layout
*/
apf.Anchoring = function(){
this.$regbase = this.$regbase | apf.__ANCHORING__;
this.$anchors = [];
var VERTICAL = 1;
var HORIZONTAL = 2;
this.$updateQueue = 0;
this.$inited =
this.$parsed =
this.$anchoringEnabled = false;
this.$hordiff =
this.$verdiff = 0;
this.$rule_v =
this.$rule_h =
this.$rule_header = "";
var l = apf.layout;
this.$supportedProperties.push("anchors");
var propHandlers = {
"right" : function(value, prop) {
if (!this.$anchoringEnabled && !this.$setLayout("anchoring"))
return;
if (!value && value !== 0)
this.$ext.style[prop] = "";
//@note Removed apf.isParsing here to activate general queuing
if (!this.$updateQueue)
l.queue(this.$pHtmlNode, this);
this.$updateQueue = this.$updateQueue | HORIZONTAL;
},
"bottom" : function(value, prop) {
if (!this.$anchoringEnabled && !this.$setLayout("anchoring"))
return;
if (!value && value !== 0)
this.$ext.style[prop] = "";
//@note Removed apf.isParsing here to activate general queuing
if (!this.$updateQueue)
l.queue(this.$pHtmlNode, this);
this.$updateQueue = this.$updateQueue | VERTICAL;
}
};
propHandlers.left = propHandlers.width = propHandlers.right;
propHandlers.top = propHandlers.height = propHandlers.bottom;
this.$propHandlers["anchors"] = function(value) {
this.$anchors = value ? value.splitSafe("(?:, *| )") : [];
if (!this.$anchoringEnabled && !this.$setLayout("anchoring"))
return;
if (!this.$updateQueue && apf.loaded)
l.queue(this.$pHtmlNode, this);
this.$updateQueue = this.$updateQueue | HORIZONTAL | VERTICAL;
};
/**
* Turns anchoring off.
*
*/
this.$disableAnchoring = function(activate) {
//!this.$parsed ||
if (!this.$inited || !this.$anchoringEnabled || !this.$pHtmlNode)
return;
l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
if (l.queue)
l.queue(this.$pHtmlNode);
for (var prop in propHandlers) {
delete this.$propHandlers[prop];
}
this.removeEventListener("DOMNodeRemoved", remove);
this.removeEventListener("DOMNodeInserted", reparent);
if (this.$ext) {
this.$ext.style.left =
this.$ext.style.right =
this.$ext.style.top =
this.$ext.style.bottom =
this.$ext.style.width =
this.$ext.style.height =
this.$ext.style.position = "";
}
/*if (this.right)
this.$ext.style.left = apf.getHtmlLeft(this.$ext) + "px";
if (this.bottom)
this.$ext.style.top = apf.getHtmlTop(this.$ext) + "px";*/
this.removeEventListener("prop.visible", visibleHandler);
this.$inited = false;
this.$anchoringEnabled = false; //isn't this redundant?
};
/**
* @attribute {Number | String} [left] Sets or gets a way to determine the amount of pixels from the left border of this element to the left edge of it's parent's border. This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
*
* ```
*/
/**
* @attribute {Number | String} [right] Sets or gets a way to determine the amount of pixels from the right border of this element to the right edge of its parent's border.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
*
* ```
*/
/**
* @attribute {Number | String} [width] Sets or gets a way to determine the amount of pixels from the left border to the right border of this element.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
*
* ```
*/
/**
* @attribute {Number | String} [top] Sets or gets a way to determine the amount of pixels from the top border of this element to the top edge of its parent's border.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
*
* ```
*/
/**
* @attribute {Number | String} [bottom] Sets or gets a way to determine the amount of pixels from the bottom border of this element to the bottom edge of its parent's border.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
*
* ```
*/
/**
* @attribute {Number | String} [height] Sets or gets a way to determine the amount of pixels from the top border to the bottom border of this element.
* This attribute can also contain percentages, arithmetic and even full expressions.
*
* #### Example
*
* ```xml
*
* ```
*/
/*
* Enables anchoring based on attributes set in the AML of this element
*/
this.$enableAnchoring = function(){
if (this.$inited) //@todo add code to reenable anchoring rules (when showing)
return;
// *** Properties and Attributes *** //
apf.extend(this.$propHandlers, propHandlers);
// *** Event handlers *** //
this.addEventListener("DOMNodeRemoved", remove);
this.addEventListener("DOMNodeInserted", reparent);
this.addEventListener("prop.visible", visibleHandler);
this.$updateQueue = 0
| ((this.left || this.width || this.right || this.anchors) && HORIZONTAL)
| ((this.top || this.height || this.bottom || this.anchors) && VERTICAL) ;
if (this.$updateQueue)
l.queue(this.$pHtmlNode, this);
this.$inited = true;
this.$anchoringEnabled = true;
};
function visibleHandler(e) {
if (!(this.$rule_header || this.$rule_v || this.$rule_h) || !this.parentNode)
return;
if (e.value) {
if (this.$rule_v || this.$rule_h) {
var rules = this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h;
l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors", rules);
//this.$ext.style.display = "none";
l.queue(this.$pHtmlNode, this);
}
l.processQueue();
}
else {
l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
l.queue(this.$pHtmlNode)
}
}
function remove(e) {
if (e && (e.$doOnlyAdmin || e.currentTarget != this))
return;
if (l.queue && this.$pHtmlNode) {
l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
l.queue(this.$pHtmlNode)
}
}
function reparent(e) {
if (!this.$amlLoaded || e.currentTarget != this)
return;
if (!e.$isMoveWithinParent && this.$parsed) //@todo hmm weird state check
this.$moveAnchoringRules(e.$oldParentHtmlNode);
//else if (e.relatedNode == this) //@todo test this
//e.currentTarget.$setLayout("anchoring");
}
this.$moveAnchoringRules = function(oldParent, updateNow) {
var rules = oldParent && l.removeRule(oldParent, this.$uniqueId + "_anchors");
if (rules)
l.queue(oldParent);
if (!this.$rule_v && !this.$rule_h && !this.$rule_header)
return;
this.$rule_header = getRuleHeader.call(this);
rules = this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h;
//@todo sometimes the content is not displayed anymore (when reparenting by xinclude)
//this.$ext.style.display = "none";
l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors", rules);
l.queue(this.$pHtmlNode, this);
};
this.$hasAnchorRules = function(){
return this.$rule_v || this.$rule_h ? true : false;
};
function getRuleHeader(){
if (!this.$pHtmlDoc) return "";
return "try{\
var oHtml = " + (apf.hasHtmlIdsInJs
? this.$ext.getAttribute("id")
: "document.getElementById('"
+ this.$ext.getAttribute("id") + "')") + ";\
\
var pWidth = " + (this.$pHtmlNode == this.$pHtmlDoc.body
? "apf.getWindowWidth()" //@todo only needed for debug?
: "apf.getHtmlInnerWidth(oHtml.parentNode)") + ";\
\
var pHeight = " + (this.$pHtmlNode == this.$pHtmlDoc.body
? "apf.getWindowHeight()" //@todo only needed for debug?
: "apf.getHtmlInnerHeight(oHtml.parentNode)") + ";\
}catch(e){\
}";
}
/**
* Sets the anchoring percentage.
* @param {String} expr An expression that's converted to a string
* @param {Number} An integer value that's used to convert to a percentage; for example, 50 becomes .5
* @returns {String} The anchor percentage
*/
function setPercentage(expr, value) {
return String(expr).replace(apf.percentageMatch, "((" + value + " * $1)/100)");
}
this.$recalcAnchoring = function(queueDelay) {
this.$updateQueue = this.$updateQueue | HORIZONTAL | VERTICAL;
this.$updateLayout();
l.queue(this.$pHtmlNode, this);
if (!queueDelay)
l.processQueue();
};
function visCheck(){
if (this.$updateQueue) {
this.$updateLayout();
apf.layout.activateRules(this.$ext.parentNode);
}
}
this.$updateLayout = function(){
if (!this.$anchoringEnabled)
return;
if (!apf.window.vManager.check(this, "anchoring", visCheck))
return;
if (!this.$parsed) {
if (!this.$ext.getAttribute("id"))
apf.setUniqueHtmlId(this.$ext);
this.$rule_header = getRuleHeader.call(this);
this.$parsed = true;
}
if (!this.$updateQueue) {
if (this.visible && this.$ext.style.display == "none")
this.$ext.style.display = "";
return;
}
if (this.draggable == "relative") {
if ("absolute|fixed|relative".indexOf(apf.getStyle(this.$ext, "position")) == -1) //@todo apf3.1 the IDE doesn't like this
this.$ext.style.position = "absolute";
}
else if (this.left || this.left === 0 || this.top || this.top === 0
|| this.right || this.right === 0 || this.bottom || this.bottom === 0
|| this.$anchors.length) {
if ("absolute|fixed".indexOf(apf.getStyle(this.$ext, "position")) == -1)
this.$ext.style.position = "absolute";
}
else if (!this.center) {
if ("absolute|fixed|relative".indexOf(apf.getStyle(this.$ext, "position")) == -1)
this.$ext.style.position = "relative";
if (!this.width)
this.$ext.style.width = "";
if (!this.height)
this.$ext.style.height = "";
}
var rules;
if (this.$updateQueue & HORIZONTAL) {
rules = [];
this.$hordiff = apf.getWidthDiff(this.$ext);
var left = this.$anchors[3] || this.left,
right = this.$anchors[1] || this.right,
width = this.width, hasLeft = left || left === 0,
hasRight = right || right === 0,
hasWidth = width || width === 0;
if (right && typeof right == "string")
right = setPercentage(right, "pWidth");
if (hasLeft) {
if (parseInt(left) != left) {
left = setPercentage(left, "pWidth");
rules.push("oHtml.style.left = (" + left + ") + 'px'");
}
else
this.$ext.style.left = left + "px";
}
if ((apf.hasStyleAnchors || !hasLeft) && hasRight) {
if (parseInt(right) != right) {
right = setPercentage(right, "pWidth");
rules.push("oHtml.style.right = (" + right + ") + 'px'");
}
else
this.$ext.style.right = right + "px";
}
if (hasLeft && hasRight) { //right != null && left != null) {
if (!apf.hasStyleAnchors)
rules.push("oHtml.style.width = (pWidth - (" + right
+ ") - (" + left + ") - " + this.$hordiff + ") + 'px'");
else
this.$ext.style.width = "";
}
else if (hasWidth && typeof this.maxwidth == "number" && typeof this.minwidth == "number") {
if (parseInt(width) != width) {
this.width = width = (this.width || "").replace(/--(\d+)/, "-(-$1)");
width = setPercentage(width, "pWidth");
rules.push("oHtml.style.width = Math.max("
+ (this.minwidth - this.$hordiff)
+ ", Math.min(" + (this.maxwidth - this.$hordiff) + ", "
+ width + " - " + this.$hordiff + ")) + 'px'");
}
else {
this.$ext.style.width = ((width > this.minwidth
? (width < this.maxwidth
? width
: this.maxwidth)
: this.minwidth) - this.$hordiff) + "px";
}
}
this.$rule_h = (rules.length
? "try{" + rules.join(";}catch(e) {};try{") + ";}catch(e){};"
: "");
}
if (this.$updateQueue & VERTICAL) {
rules = [];
this.$verdiff = apf.getHeightDiff(this.$ext);
var top = this.$anchors[0] || this.top,
bottom = this.$anchors[2] || this.bottom,
height = this.height, hasTop = top || top === 0,
hasBottom = bottom || bottom === 0,
hasHeight = height || height === 0;
if (bottom && typeof bottom == "string")
bottom = setPercentage(bottom, "pHeight");
if (hasTop) {
if (parseInt(top) != top) {
top = setPercentage(top, "pHeight");
rules.push("oHtml.style.top = (" + top + ") + 'px'");
}
else
this.$ext.style.top = top + "px";
}
if ((apf.hasStyleAnchors || !hasTop) && hasBottom) {
if (parseInt(bottom) != bottom) {
rules.push("oHtml.style.bottom = (" + bottom + ") + 'px'");
}
else
this.$ext.style.bottom = bottom + "px";
}
if (hasTop && hasBottom) { //bottom != null && top != null) {
if (!apf.hasStyleAnchors)
rules.push("oHtml.style.height = (pHeight - (" + bottom +
") - (" + top + ") - " + this.$verdiff + ") + 'px'");
else
this.$ext.style.height = "";
}
else if (hasHeight && typeof this.minheight == "number") {
if (parseInt(height) != height) {
height = setPercentage(height, "pHeight");
rules.push("oHtml.style.height = Math.max("
+ (this.minheight - this.$verdiff)
+ ", Math.min(" + (this.maxheight - this.$verdiff) + ", "
+ height + " - " + this.$verdiff + ")) + 'px'");
}
else {
this.$ext.style.height = Math.max(0, (height > this.minheight
? (height < this.maxheight
? height
: this.maxheight)
: this.minheight) - this.$verdiff) + "px";
}
}
this.$rule_v = (rules.length
? "try{" + rules.join(";}catch(e) {};try{") + ";}catch(e){};"
: "");
}
if (this.$rule_v || this.$rule_h) {
l.setRules(this.$pHtmlNode, this.$uniqueId + "_anchors",
this.$rule_header + "\n" + this.$rule_v + "\n" + this.$rule_h, true);
}
else {
l.removeRule(this.$pHtmlNode, this.$uniqueId + "_anchors");
}
this.$updateQueue = 0;
if (this.$box && !apf.hasFlexibleBox) //temporary fix
apf.layout.forceResize(this.$ext);
};
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
//if (this.$updateQueue)
//this.$updateLayout();
});
this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
this.$disableAnchoring();
});
};
apf.__CONTENTEDITABLE__ = 1 << 24;
apf.__GUIELEMENT__ = 1 << 15;
apf.__VALIDATION__ = 1 << 6;
/**
* All elements inheriting from this {@link term.baseclass baseclass} are an AML component.
*
* @class apf.GuiElement
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.4
*
* @baseclass
* @inherits apf.AmlElement
* @inherits apf.Anchoring
* @inherits apf.DelayedRender
* @inherits apf.DragDrop
* @inherits apf.Focussable
* @inherits apf.Interactive
* @inherits apf.Validation
*
*/
/**
* @attribute {String} span Sets or gets the number of columns that this element spans. Only used inside a table element.
*/
/**
* @attribute {String | Number} margin Sets or gets margin values.
*
* Set these sizes as a quarter of strings, in the usual top, right, bottom, left sequence, or pass an empty string to turn off margins.
*/
/**
* @attribute {String} align Sets or gets the edge of the parent to which this
* element aligns.
*
* The possible values are a combination of "left", "middle", "right", "top", "bottom" and "slider" ,and optionally a size.
* Combinations are combined with the pipe (`"|"`) character.
*
*/
/**
* @attribute {Mixed} left Sets or gets the left position of this element. Depending
* on the choosen layout method the unit can be pixels, a percentage or an
* expression.
*/
/**
* @attribute {Mixed} top Sets or gets the top position of this element. Depending
* on the choosen layout method the unit can be pixels, a percentage or an
* expression.
*/
/**
* @attribute {Mixed} right Sets or gets the right position of this element. Depending
* on the choosen layout method the unit can be pixels, a percentage or an
* expression.
*/
/**
* @attribute {Mixed} bottom Sets or gets the bottom position of this element. Depending
* on the choosen layout method the unit can be pixels, a percentage or an
* expression.
*/
/**
* @attribute {Mixed} width Sets or gets the different between the left edge and the
* right edge of this element. Depending on the choosen layout method the
* unit can be pixels, a percentage or an expression.
*
* #### Remarks
*
* When used as a child of a grid element the width can also be set as '*'.
* This will fill the rest space.
*/
/**
* @attribute {Mixed} height Sets or gets the different between the top edge and the
* bottom edge of this element. Depending on the choosen layout method the
* unit can be pixels, a percentage or an expression.
*
* #### Remarks
*
* When used as a child of a grid element the height can also be set as '*'.
* This will fill the rest space.
*/
/**
* @event resize Fires when the element changes width or height.
*/
/**
* @event contextmenu Fires when the user requests a context menu, either
* using the keyboard or mouse.
* @bubbles
* @cancelable Prevents the default context menu from appearing.
* @param {Object} e The standard event object. Contains the following properties:
* - x ([[Number]]): The x coordinate where the contextmenu is requested on
* - y ([[Number]]): The y coordinate where the contextmenu is requested on
* - htmlEvent ([[Event]]): The HTML event object that triggered this event from being called
*/
/**
* @event focus Fires when this element receives focus.
*/
/**
* @event blur Fires when this element loses focus.
*/
/**
* @event keydown Fires when this element has focus and the user presses a key on the keyboard.
* @bubbles
* @cancelable Prevents the default key action.
* @param {Object} e The standard event object. Contains the following properties:
* - ctrlKey ([[Boolean]]): Specifies whether the [[keys: Ctrl]] key was pressed
* - shiftKey ([[Boolean]]): Specifies whether the [[keys: Shift]] key was pressed
* - altKey ([[Boolean]]): Specifies whether the [[keys: Alt ]] key was pressed
* - keyCode ([[Number]]): Indicates which key was pressed. This is an ascii number
* - htmlEvent ([[Event]]): the HTML event object that triggered this event from being called
*
*/
apf.GuiElement = function(){
this.$init(true);
};
(function(){
this.$regbase = this.$regbase | apf.__GUIELEMENT__;
this.$focussable = apf.KEYBOARD_MOUSE; // Each GUINODE can get the focus by default
this.visible = 2; //default value;
this.minwidth = 0;
this.minheight = 0;
/*this.minwidth = 5;
this.minheight = 5;
this.maxwidth = 10000;
this.maxheight = 10000;*/
this.$booleanProperties["disable-keyboard"] = true;
this.$booleanProperties["visible"] = true;
/**
* @attribute {Boolean} draggable If true, the element can be dragged around the screen.
*/
/**
* @attribute {Boolean} resizable If true, the element can by resized by the user.
*
*/
this.$supportedProperties.push("draggable", "resizable");
this.$supportedProperties.push(
"focussable", "zindex", "disabled", "tabindex",
"disable-keyboard", "contextmenu", "visible", "autosize",
"loadaml", "actiontracker", "alias",
"width", "left", "top", "height", "tooltip"
);
this.$setLayout = function(type, insert) {
if (!this.$drawn || !this.$pHtmlNode)
return false;
if (this.parentNode) {
if (this.parentNode.localName == "table") {
if (this.$disableCurrentLayout)
this.$disableCurrentLayout();
this.parentNode.register(this, insert);
this.$disableCurrentLayout = null;
this.$layoutType = null;
return type == "table";
}else
if (this.parentNode.$box) {
if (this.$layoutType != this.parentNode) {
if (this.$disableCurrentLayout)
this.$disableCurrentLayout();
this.parentNode.register(this, insert);
this.$disableCurrentLayout = null;
this.$layoutType = this.parentNode;
}
return type == this.parentNode.localName;
} //else
}
if (!this.$anchoringEnabled) {
if (this.$disableCurrentLayout)
this.$disableCurrentLayout();
this.$enableAnchoring();
this.$disableCurrentLayout = this.$disableAnchoring;
this.$layoutType = null;
}
return type == "anchoring";
}
this.addEventListener("DOMNodeInserted", function(e) {
if (e.currentTarget == this
&& (this.parentNode.$box || "table" == this.parentNode.localName)) {
if (!e.$oldParent) this.$layoutType = null;
this.$setLayout(!e.$oldParent);
}
});
this.implement(
apf.Anchoring
);
// **** Convenience functions for gui nodes **** //
// *** Geometry *** //
/**
* Sets the difference between the left edge and the right edge of this
* element.
*
* Depending on the choosen layout method, the unit can be
* pixels, a percentage, or an expression.
*
* @chainable
* @param {Number | String} value The new width of this element.
*/
this.setWidth = function(value) {
this.setProperty("width", value, false, true);
return this;
};
/**
* Sets the different between the top edge and the bottom edge of this
* element.
*
* Depending on the choosen layout method the unit can be
* pixels, a percentage or an expression.
*
* @chainable
* @param {Number | String} value the new height of this element.
*/
this.setHeight = function(value) {
this.setProperty("height", value, false, true);
return this;
};
/**
* Sets the left position of this element.
*
* Depending on the choosen layout method the unit can be pixels,
* a percentage or an expression.
*
* @chainable
* @param {Number | String} value The new left position of this element.
*/
this.setLeft = function(value) {
this.setProperty("left", value, false, true);
return this;
};
/**
* Sets the top position of this element.
*
* Depending on the choosen layout method the unit can be pixels,
* a percentage or an expression.
*
* @chainable
* @param {Number | String} value The new top position of this element.
*/
this.setTop = function(value) {
this.setProperty("top", value, false, true);
return this;
};
if (!this.show) {
/**
* Makes the elements visible.
* @chainable
*/
this.show = function(){
this.setProperty("visible", true, false, true);
return this;
};
}
if (!this.hide) {
/**
* Makes the elements invisible.
* @chainable
*/
this.hide = function(){
this.setProperty("visible", false, false, true);
return this;
};
}
/**
* Retrieves the calculated width in pixels for this element.
*/
this.getWidth = function(){
return (this.$ext || {}).offsetWidth;
};
/**
* Retrieves the calculated height in pixels for this element.
*/
this.getHeight = function(){
return (this.$ext || {}).offsetHeight;
};
/**
* Retrieves the calculated left position in pixels for this element,
* relative to the offsetParent.
*/
this.getLeft = function(){
return (this.$ext || {}).offsetLeft;
};
/**
* Retrieves the calculated top position in pixels for this element,
* relative to the offsetParent.
*/
this.getTop = function(){
return (this.$ext || {}).offsetTop;
};
// *** Disabling *** //
/**
* Activates the functions of this element.
* @chainable
*/
this.enable = function(){
this.setProperty("disabled", false, false, true);
return this;
};
/**
* Deactivates the functions of this element.
* @chainable
*/
this.disable = function(){
this.setProperty("disabled", true, false, true);
return this;
};
// *** z-Index *** //
/**
* Moves this element to the lowest z ordered level.
* @chainable
*/
this.sendToBack = function(){
this.setProperty("zindex", 0, false, true);
return this;
};
/**
* Moves this element to the highest z ordered level.
* @chainable
*/
this.bringToFront = function(){
this.setProperty("zindex", apf.all.length + 1, false, true);
return this;
};
/**
* Moves this element one z order level deeper.
* @chainable
*/
this.sendBackwards = function(){
this.setProperty("zindex", this.zindex - 1, false, true);
return this;
};
/**
* Moves this element one z order level higher.
* @chainable
*/
this.bringForward = function(){
this.setProperty("zindex", this.zindex + 1, false, true);
return this;
};
this.hasFocus = function(){}
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
var x = this.$aml;
// will $pHtmlNode be deprecated soon?
// check used to be:
//if (!this.$pHtmlNode && this.parentNode)
if (this.parentNode) {
if (this.localName == "item"
&& this.parentNode.hasFeature(apf.__MULTISELECT__)) //special case for item nodes, using multiselect rendering
this.$pHtmlNode = this.parentNode.$container;
else
this.$pHtmlNode = this.parentNode.$int; //@todo apf3.0 change this in the mutation events
}
if (!this.$pHtmlNode) //@todo apf3.0 retry on DOMNodeInserted
return;
this.$pHtmlDoc = this.$pHtmlNode.ownerDocument || document;
if (this.$initSkin)
this.$initSkin(x);
if (this.$draw)
this.$draw();
if (e.id)
this.$ext.setAttribute("id", e.id);
if (typeof this.visible == "undefined")
this.visible = true;
this.$drawn = true;
}, true);
var f = function(e) {
if (!this.$pHtmlNode) //@todo apf3.0 retry on DOMInsert or whatever its called
return;
this.$setLayout(); //@todo apf3.0 moving an element minwidth/height should be recalced
//@todo apf3.0 set this also for skin change
if (this.$ext) {
var hasPres = (this.hasFeature(apf.__PRESENTATION__)) || false;
var type = this.$isLeechingSkin ? this.localName : "main";
this.minwidth = Math.max(this.minwidth || 0, apf.getCoord(hasPres && parseInt(this.$getOption(type, "minwidth")), 0));
this.minheight = Math.max(this.minheight || 0, apf.getCoord(hasPres && parseInt(this.$getOption(type, "minheight")), 0));
if (this.maxwidth == undefined)
this.maxwidth = apf.getCoord(hasPres && parseInt(this.$getOption(type, "maxwidth")), 10000);
if (this.maxheight == undefined)
this.maxheight = apf.getCoord(hasPres && parseInt(this.$getOption(type, "maxheight")), 10000);
//--#ifdef __WITH_CONTENTEDITABLE
//@todo slow??
if (this.minwidth || this.minheight || this.maxwidth != 10000 || this.maxheight != 10000) {
var diff = apf.getDiff(this.$ext);
if (this.minwidth)
this.$ext.style.minWidth = Math.max(0, this.minwidth - diff[0]) + "px";
if (this.minheight)
this.$ext.style.minHeight = Math.max(0, this.minheight - diff[1]) + "px";
if (this.maxwidth != 10000)
this.$ext.style.maxWidth = Math.max(0, this.maxwidth - diff[0]) + "px";
if (this.maxheight != 10000)
this.$ext.style.maxHeight = Math.max(0, this.maxheight - diff[1]) + "px";
if (this.$altExt && apf.isGecko) {
this.$altExt.style.minHeight = this.$ext.style.minHeight;
this.$altExt.style.maxHeight = this.$ext.style.maxHeight;
this.$altExt.style.minWidth = this.$ext.style.minWidth;
this.$altExt.style.maxWidth = this.$ext.style.maxWidth;
}
}
//--#endif
}
if (this.$loadAml)
this.$loadAml(this.$aml); //@todo replace by event
if (this.$focussable && typeof this.focussable == "undefined")
apf.GuiElement.propHandlers.focussable.call(this, true);
if (setResizeEvent)
f2();
};
this.addEventListener("DOMNodeInsertedIntoDocument", f);
this.addEventListener("$skinchange", f);
var f2, setResizeEvent;
this.addEventListener("$event.resize", f2 = function(c) {
if (!this.$ext) {
setResizeEvent = true;
return;
}
apf.layout.setRules(this.$ext, "resize", "var o = apf.all[" + this.$uniqueId + "];\
if (o) o.dispatchEvent('resize');", true);
apf.layout.queue(this.$ext);
//apf.layout.activateRules(this.$ext);
this.removeEventListener("$event.resize", f2);
});
this.addEventListener("contextmenu", function(e) {
if (!this.contextmenus) return;
if (this.hasFeature(apf.__DATABINDING__)) {
var contextmenu;
var xmlNode = this.hasFeature(apf.__MULTISELECT__)
? this.selected
: this.xmlRoot;
var i, l, m, isRef, sel, menuId, cm, result;
for (i = 0, l = this.contextmenus.length; i < l; i++) {
isRef = (typeof (cm = this.contextmenus[i]) == "string");
result = null;
if (!isRef && cm.match && xmlNode) {//@todo apf3.0 cache this statement
result = (cm.cmatch || (cm.cmatch = apf.lm.compile(cm.match, {
xpathmode: 3,
injectself: true
})))(xmlNode)
}
if (isRef || xmlNode && result || !cm.match) { //!xmlNode &&
menuId = isRef
? cm
: cm.menu;
var menu = cm.localName == "menu" ? cm : self[menuId];
if (!menu) {
return;
}
menu.display(e.x, e.y, null, this, xmlNode);
e.returnValue = false;//htmlEvent.
e.cancelBubble = true;
break;
}
}
//IE6 compatiblity
/*
@todo please test that disabling this is OK
if (!apf.config.disableRightClick) {
document.oncontextmenu = function(){
document.oncontextmenu = null;
e.cancelBubble = true;
return false;
}
}*/
}
else {
var menu;
if (typeof this.contextmenus[0] == "string")
menu = self[this.contextmenus[0]];
if (this.contextmenus[0].localName == "menu")
menu = this.contextmenus[0];
else
menu = self[this.contextmenus[0].getAttribute("menu")];
if (!menu) {
return;
}
menu.display(e.x, e.y, null, this);
e.returnValue = false;//htmlEvent.
e.cancelBubble = true;
}
});
}).call(apf.GuiElement.prototype = new apf.AmlElement());
/*
* @for apf.amlNode
* @private
*/
apf.GuiElement.propHandlers = {
/**
* @attribute {Number} minwidth Sets or gets the minimum width for this element.
*/
/**
* @attribute {Number} maxwidth Sets or gets the maximum width for this element.
*/
/**
* @attribute {Number} minheight Sets or gets the minimum height for this element.
*/
/**
* @attribute {Number} maxheight Sets or gets the maximum height for this element.
*/
"minwidth": function(value){ this.$ext.style.minWidth = Math.max(0, value - apf.getWidthDiff(this.$ext)) + "px"; },
"minheight": function(value){ this.$ext.style.minHeight = Math.max(0, value - apf.getHeightDiff(this.$ext)) + "px"; },
"maxwidth": function(value){ this.$ext.style.maxWidth = Math.max(0, value - apf.getWidthDiff(this.$ext)) + "px"; },
"maxheight": function(value){ this.$ext.style.maxHeight = Math.max(0, value - apf.getHeightDiff(this.$ext)) + "px"; },
/**
* @attribute {Boolean} focussable Sets or gets whether this element can receive the focus.
* The focused element receives keyboard event.
*/
"focussable": function(value) {
this.focussable = typeof value == "undefined" || value;
if (value == "container") {
this.$isWindowContainer = true;
this.focussable = true;
}
else
this.focussable = apf.isTrue(value);
if (!this.hasFeature(apf.__FOCUSSABLE__)) //@todo should this be on the prototype
this.implement(apf.Focussable);
if (this.focussable) {
apf.window.$addFocus(this, this.tabindex);
if (value == "container")
this.$tabList.remove(this);
}
else {
apf.window.$removeFocus(this);
}
},
/**
* @attribute {Number} tabindex Sets or gets the tab index for this element.
*/
"tabindex": function(value) {
if (!this.hasFeature(apf.__FOCUSSABLE__))
return;
this.setTabIndex(parseInt(value) || null);
},
/**
* @attribute {Number} zindex Sets or gets the z ordered layer in which this element is
* drawn.
*/
"zindex": function(value) {
this.$ext.style.zIndex = value;
},
/**
* @attribute {Boolean} visible Sets or gets whether this element is shown.
*/
"visible": function(value) {
if (apf.isFalse(value) || typeof value == "undefined") {
if (this.$ext)
this.$ext.style.display = "none";
if (apf.document.activeElement == this || this.canHaveChildren == 2
&& apf.isChildOf(this, apf.document.activeElement, false)) {
if (apf.config.allowBlur && this.hasFeature(apf.__FOCUSSABLE__))
this.blur();
else
apf.window.moveNext();
}
this.visible = false;
}
else { //if (apf.isTrue(value)) default
if (this.$ext) {
this.$ext.style.display = ""; //Some form of inheritance detection
if (!this.$ext.offsetHeight)
this.$ext.style.display = this.$display || "block";
}
// if (apf.layout && this.$int) //apf.hasSingleRszEvent)
// apf.layout.forceResize(this.$int);//this.$int
this.visible = true;
}
},
/**
* @attribute {Boolean} disabled Sets or gets whether this element's functions are active.
* For elements that can contain other `apf.NODE_VISIBLE` elements, this
* attribute applies to all its children.
*/
"disabled": function(value) {
if (!this.$drawn) {
var _self = this;
//this.disabled = false;
this.addEventListener("DOMNodeInsertedIntoDocument",
this.$updateDisabled || (this.$updateDisabled = function(e) {
apf.GuiElement.propHandlers.disabled.call(_self, _self.disabled);
}));
return;
}
else
apf.queue.remove("disable" + this.$uniqueId);
//For child containers we only disable its children
if (this.canHaveChildren) {
//@todo Fix focus here first.. else it will jump whilst looping
if (value != -1)
value = this.disabled = apf.isTrue(value);
var nodes = this.childNodes;
for (var node, i = 0, l = nodes.length; i < l; i++) {
node = nodes[i];
if (node.nodeFunc == apf.NODE_VISIBLE) {
if (value && node.disabled != -1)
node.$disabled = node.disabled || false;
node.setProperty("disabled", value ? -1 : false);
}
}
//this.disabled = undefined;
if (this.$isWindowContainer)
return;
}
if (value == -1 || value == false) {
//value = true;
}
else if (typeof this.$disabled == "boolean") {
if (value === null) {
value = this.$disabled;
this.$disabled = null;
}
else {
this.$disabled = value || false;
return;
}
}
if (apf.isTrue(value) || value == -1) {
this.disabled = false;
if (apf.document.activeElement == this) {
apf.window.moveNext(true); //@todo should not include window
if (apf.document.activeElement == this)
this.$blur();
}
if (this.hasFeature(apf.__PRESENTATION__))
this.$setStyleClass(this.$ext, this.$baseCSSname + "Disabled");
if (this.$disable)
this.$disable();
this.disabled = value;
}
else {
if (this.hasFeature(apf.__DATABINDING__) && apf.config.autoDisable
&& !(!this.$bindings || this.xmlRoot))
return false;
this.disabled = false;
if (apf.document.activeElement == this)
this.$focus();
if (this.hasFeature(apf.__PRESENTATION__))
this.$setStyleClass(this.$ext, null, [this.$baseCSSname + "Disabled"]);
if (this.$enable)
this.$enable();
}
},
/**
* @attribute {Boolean} enables Sets or gets whether this element's functions are active.
* For elements that can contain other `apf.NODE_VISIBLE` elements, this
* attribute applies to all its children.
*/
"enabled" : function(value) {
this.setProperty("disabled", !value);
},
/**
* @attribute {Boolean} disable-keyboard Sets or gets whether this element receives
* keyboard input. This allows you to disable keyboard independently from
* focus handling.
*/
"disable-keyboard": function(value) {
this.disableKeyboard = apf.isTrue(value);
},
/**
* @attribute {String} tooltip Sets or gets the text displayed when a user hovers with
* the mouse over the element.
*/
"tooltip" : function(value) {
this.$ext.setAttribute("title", (value || "") + (this.hotkey ? " ("
+ (apf.isMac ? apf.hotkeys.toMacNotation(this.hotkey) : this.hotkey) + ")" : ""));
},
/**
* @attribute {String} contextmenu Sets or gets the name of the menu element that will
* be shown when the user right clicks or uses the context menu keyboard
* shortcut.
*
* #### Example
*
* ```xml
*
* test
* test2
*
*
*
*
* ```
*/
"contextmenu": function(value) {
this.contextmenus = [value];
},
/**
* @attribute {String} actiontracker Sets or gets the name of the [[apf.actiontracker action tracker]] that
* is used for this element and its children. If the actiontracker doesn't
* exist yet, it is created.
*
* #### Example
*
* In this example, the list uses a different action tracker than the two
* textboxes which determine their actiontracker based on the one that
* is defined on the bar.
*
* ```xml
*
*
*
*
*
*
* ```
*/
"actiontracker": function(value) {
if (!value) {
this.$at = null;
}
else if (typeof value == "object") {
this.$at = value;
}
else {
this.$at = typeof value == "string" && self[value]
? apf.nameserver.get("actiontracker", value) || self[value].getActionTracker()
: apf.setReference(value,
apf.nameserver.register("actiontracker",
value, new apf.actiontracker()));
if (!this.$at.name)
this.$at.name = value;
}
},
//Load subAML
/**
* @attribute {String} aml Sets or gets the {@link term.datainstruction data instruction}
* that loads new AML as children of this element.
*/
"aml": function(value) {
this.replaceMarkup(value);
}
/*
* @attribute {String} sets this aml element to be editable
* that loads new aml as children of this element.
*/
// @todo Doc WTF?
};
apf.__PRESENTATION__ = 1 << 9;
/**
* All elements inheriting from this {@link term.baseclass baseclass} have skinning features. A skin is a description
* of how the element is rendered. In the web browser, this is done using HTML
* elements and CSS.
*
* #### Remarks
*
* The skin is set using the `skin` attribute. The skin of each element can be
* changed at run time. Other than just changing the look of an element, a skin
* change can help the user to perceive information in a different way. For
* example, a list element has a default skin, but can also use the thumbnail
* skin to display thumbnails of the {@link term.datanode data nodes}.
*
* #### Example
*
* A skin for an element is always built up out of a standard set of parts:
*
* ```xml
*
*
* ...
*
*
*
*
*
* ...
*
* ...
*
*
* ```
*
* The alias contains a name that contains alternative names for the skin. The
* style tags contain the CSS. The main tag contains the HTML elements that are
* created when the component is created. Any other skin items are used to render
* other elements of the widget. In this reference guide you will find these
* skin items described on the pages of each widget.
*
* @class apf.Presentation
* @define presentation
* @inherits apf.GuiElement
* @baseclass
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.5
*/
apf.Presentation = function(){
this.$init(true);
};
(function(){
this.$regbase = this.$regbase | apf.__PRESENTATION__;
// *** Properties and Attributes *** //
this.$supportedProperties.push("skin");
/**
* @attribute {String} skinset Sets or gets the skinset for
* this element. If none are specified ,the `skinset` attribute
* of the app settings is used. When that's not defined, the default skinset
* is used.
*
* #### Example
*
* ```xml
*
* ```
*/
this.$propHandlers["skinset"] =
/**
* @attribute {String} skin Sets or gets the name of the skin in the skinset that defines
* how this element is rendered. When a skin is changed, the full state of the
* element is kept, including its selection, all the
* AML attributes, loaded data, and focus and disabled states.
*
* #### Example
*
* In XML:
*
* ```xml
*
* ```
*
* Or, in JavaScript:
*
* ```javascript
* lstExample.setAttribute("skin", "list");
* ```
*/
this.$propHandlers["skin"] = function(value) {
if (!this.$amlLoaded) //If we didn't load a skin yet, this will be done when we attach to a parent
return;
if (!this.$skinTimer) {
var _self = this;
clearTimeout(this.$skinTimer);
this.$skinTimer = $setTimeout(function(){
changeSkin.call(_self, _self.skin);
delete _self.$skinTimer;
});
}
}
/**
* @attribute {String} style Sets or gets the CSS style applied to the this element. This can be a string containing one or more CSS rules.
*/
this.$propHandlers["style"] = function(value) {
if (!this.styleAttrIsObj && this.$amlLoaded)
this.$ext.setAttribute("style", value);
}
/**
* @attribute {String} border Sets or gets border values. Set these sizes as a quarter of strings, in the usual top, right, bottom, left sequence, or pass an empty string to turn off borders.
*/
this.$propHandlers["border"] = function(value) {
if (!value)
this.$ext.style.borderWidth = "";
else
this.$ext.style.borderWidth = apf.getBox(value).join("px ") + "px";
}
/**
* @attribute {String | Number} margin Sets or gets margin values. Set these sizes as a quarter of strings, in the usual top, right, bottom, left sequence, or pass an empty string to turn off margins.
*/
this.$propHandlers["margin"] = function(value) {
if (!value)
this.$ext.style.margin = "";
else
this.$ext.style.margin = apf.getBox(value).join("px ") + "px";
}
/**
* @attribute {String} class Sets or gets the name of the CSS style class applied to this element.
*/
this.$propHandlers["class"] = function(value) {
this.$setStyleClass(this.$ext, value, this.$lastClassValue ? [this.$lastClassValue] : null);
this.$lastClassValue = value;
}
this.$forceSkinChange = function(skin, skinset) {
changeSkin.call(this, skin, skinset);
}
//@todo objects don't always have an $int anymore.. test this
function changeSkin(skin, skinset) {
clearTimeout(this.$skinTimer);
//var skinName = (skinset || this.skinset || apf.config.skinset)
// + ":" + (skin || this.skin || this.localName);
//Store selection
if (this.selectable)
var valueList = this.getSelection();//valueList;
//Store needed state information
var oExt = this.$ext,
oInt = this.$int,
pNode = this.$ext ? this.$ext.parentNode : this.$pHtmlNode,
beforeNode = oExt && oExt.nextSibling,
idExt = this.$ext && this.$ext.getAttribute("id"),
idInt = this.$int && this.$int.getAttribute("id"),
oldBase = this.$baseCSSname;
if (oExt && oExt.parentNode)
oExt.parentNode.removeChild(oExt);
//@todo changing skin will leak A LOT, should call $destroy here, with some extra magic
if (this.$destroy)
this.$destroy(true);
//Load the new skin
this.skin = skin;
this.$loadSkin(skinset ? skinset + ":" + skin : null);
//Draw
if (this.$draw)
this.$draw(true);
if (idExt)
this.$ext.setAttribute("id", idExt);
if (beforeNode || this.$ext && pNode != this.$ext.parentNode)
pNode.insertBefore(this.$ext, beforeNode);
//Style
//Border
//Margin
if (this.$ext) {
//Classes
var i, l, newclasses = [],
classes = (oExt.className || "").splitSafe("\\s+");
for (i = 0; i < classes.length; i++) {
if (classes[i] && classes[i].indexOf(oldBase) != 0)
newclasses.push(classes[i].replace(oldBase, this.$baseCSSname));
}
apf.setStyleClass(this.$ext, newclasses.join(" "));
//Copy events
var en, ev = apf.skins.events;
for (i = 0, l = ev.length; i < l; i++) {
en = ev[i];
if (typeof oExt[en] == "function" && !this.$ext[en])
this.$ext[en] = oExt[en];
}
//Copy css state (dunno if this is best)
this.$ext.style.left = oExt.style.left;
this.$ext.style.top = oExt.style.top;
this.$ext.style.width = oExt.style.width;
this.$ext.style.height = oExt.style.height;
this.$ext.style.right = oExt.style.right;
this.$ext.style.bottom = oExt.style.bottom;
this.$ext.style.zIndex = oExt.style.zIndex;
this.$ext.style.position = oExt.style.position;
this.$ext.style.display = oExt.style.display;
}
//Widget specific
//if (this.$loadAml)
//this.$loadAml(this.$aml);
if (idInt)
this.$int.setAttribute("id", idInt);
if (this.$int && this.$int != oInt) {
var node, newNode = this.$int, nodes = oInt.childNodes;
for (var i = nodes.length - 1; i >= 0; i--) {
if ((node = nodes[i]).host) {
node.host.$pHtmlNode = newNode;
if (node.host.$isLeechingSkin)
setLeechedSkin.call(node.host);
}
newNode.insertBefore(node, newNode.firstChild);
}
//this.$int.onresize = oInt.onresize;
}
//DragDrop
if (this.hasFeature(apf.__DRAGDROP__)) {
if (document.elementFromPointAdd) {
document.elementFromPointRemove(oExt);
document.elementFromPointAdd(this.$ext);
}
}
//Check disabled state
if (this.disabled)
this.$disable(); //@todo apf3.0 test
//Check focussed state
if (this.$focussable && apf.document.activeElement == this)
this.$focus(); //@todo apf3.0 test
//Dispatch event
this.dispatchEvent("$skinchange", {
ext: oExt,
"int": oInt
});
//Reload data
if (this.hasFeature(apf.__DATABINDING__) && this.xmlRoot)
this.reload();
else
if (this.value)
this.$propHandlers["value"].call(this, this.value);
//Set Selection
if (this.hasFeature(apf.__MULTISELECT__)) {
if (this.selectable)
this.selectList(valueList, true);
}
//Move layout rules
if (!apf.hasSingleRszEvent) {
apf.layout.activateRules(this.$ext);
if (this.$int)
apf.layout.activateRules(this.$int);
}
/*
if (this.hasFeature(apf.__ANCHORING__))
this.$recalcAnchoring();
if (this.hasFeature(apf.__ALIGNMENT__)) {
if (this.aData)
this.aData.oHtml = this.$ext;
if (this.pData) {
this.pData.oHtml = this.$ext;
this.pData.pHtml = this.$int;
var nodes = this.pData.childNodes;
for (i = 0; i < nodes.length; i++)
nodes[i].pHtml = this.$int; //Should this be recursive??
}
}
*/
if (this.draggable && this.$propHandlers["draggable"]) //@todo move these to the event below apf3.0)
this.$propHandlers["draggable"].call(this, this.draggable);
if (this.resizable && this.$propHandlers["resizable"])
this.$propHandlers["resizable"].call(this, this.resizable);
apf.layout.forceResize(this.$ext);
};
// *** Private methods *** //
this.$setStyleClass = apf.setStyleClass;
function setLeechedSkin(e) {
if (!this.$amlLoaded || e && (e.$isMoveWithinParent
|| e.currentTarget != this || !e.$oldParent))
return;
this.$setInheritedAttribute(this, "skinset");
if (this.attributes.getNamedItem("skin"))
return;
//e.relatedNode
var skinName, pNode = this.parentNode, skinNode;
if ((skinName = this.$canLeechSkin.dataType
== apf.STRING ? this.$canLeechSkin : this.localName)
&& pNode.$originalNodes
&& (skinNode = pNode.$originalNodes[skinName])
&& skinNode.getAttribute("inherit")) {
var link = skinNode.getAttribute("link");
this.$isLeechingSkin = true;
if (link) {
this.$forceSkinChange(link);
}
else {
var skin = pNode.skinName.split(":");
this.$forceSkinChange(skin[1], skin[0]);
}
}
else if (this.$isLeechingSkin) {
delete this.skin;
this.$isLeechingSkin = false;
this.$forceSkinChange();
}
}
//Skin Inheritance
//@todo Probably requires some cleanup
this.$initSkin = function(x) {
if (this.$canLeechSkin) {
this.addEventListener("DOMNodeInserted", setLeechedSkin);
}
if (!this.skin)
this.skin = this.getAttribute("skin");
var skinName, pNode = this.parentNode, skinNode;
if (this.$canLeechSkin && !this.skin
&& (skinName = this.$canLeechSkin.dataType == apf.STRING
? this.$canLeechSkin
: this.localName)
&& pNode && pNode.$originalNodes
&& (skinNode = pNode.$originalNodes[skinName])
&& skinNode.getAttribute("inherit")) {
var link = skinNode.getAttribute("link");
this.$isLeechingSkin = true;
if (link) {
this.skin = link;
this.$loadSkin();
}
else {
this.$loadSkin(pNode.skinName);
}
}
else {
if (!this.skinset)
this.skinset = this.getAttribute("skinset");
this.$loadSkin(null, this.$canLeechSkin);
}
}
/*
* Initializes the skin for this element when none has been set up.
*
* @param {String} skinName Identifier for the new skin (for example: 'default:List' or 'win').
* @param {Boolean} [noError]
*/
this.$loadSkin = function(skinName, noError) {
//this.skinName || //where should this go and why?
this.baseSkin = skinName || (this.skinset
|| this.$setInheritedAttribute("skinset"))
+ ":" + (this.skin || this.localName);
clearTimeout(this.$skinTimer);
if (this.skinName) {
this.$blur();
this.$baseCSSname = null;
}
this.skinName = this.baseSkin; //Why??
//this.skinset = this.skinName.split(":")[0];
this.$pNodes = {}; //reset the this.$pNodes collection
this.$originalNodes = apf.skins.getTemplate(this.skinName, true);
if (!this.$originalNodes) {
// console.warn("Possible missing skin: ", this.baseSkin);
var skin = this.skin;
if (skin) {
var skinset = this.skinName.split(":")[0];
this.baseName = this.skinName = "default:" + skin;
this.$originalNodes = apf.skins.getTemplate(this.skinName);
if (!this.$originalNodes && skinset != "default") {
this.baseName = this.skinName = skinset + ":" + this.localName;
this.$originalNodes = apf.skins.getTemplate(this.skinName, true);
}
}
if (!this.$originalNodes) {
this.baseName = this.skinName = "default:" + this.localName;
this.$originalNodes = apf.skins.getTemplate(this.skinName);
}
if (!this.$originalNodes) {
if (noError) {
return (this.baseName = this.skinName =
this.originalNode = null);
}
throw new Error(apf.formatErrorString(1077, this,
"Presentation",
"Could not load skin: " + this.baseSkin));
}
//this.skinset = this.skinName.split(":")[0];
}
if (this.$originalNodes)
apf.skins.setSkinPaths(this.skinName, this);
};
this.$getNewContext = function(type, amlNode) {
this.$pNodes[type] = this.$originalNodes[type].cloneNode(true);
};
this.$hasLayoutNode = function(type) {
return this.$originalNodes[type] ? true : false;
};
this.$getLayoutNode = function(type, section, htmlNode) {
var node = this.$pNodes[type] || this.$originalNodes[type];
if (!node) {
return false;
}
if (!section)
return htmlNode || apf.getFirstElement(node);
var textNode = node.getAttribute(section);
if (!textNode)
return null;
return (htmlNode
? apf.queryNode(htmlNode, textNode)
: apf.getFirstElement(node).selectSingleNode(textNode));
};
this.$getOption = function(type, section) {
type = type.toLowerCase(); //HACK: lowercasing should be solved in the comps.
//var node = this.$pNodes[type];
var node = this.$pNodes[type] || this.$originalNodes[type];
if (!section || !node)
return node;//apf.getFirstElement(node);
//var option = node.selectSingleNode("@" + section);
//option = option ? option.value : ""
var attr = node.getAttribute(section) || ""
//if (option != attr)
// debugger
return attr;
};
this.$getExternal = function(tag, pNode, func, aml) {
if (!pNode)
pNode = this.$pHtmlNode;
if (!tag)
tag = "main";
//if (!aml)
//aml = this.$aml;
tag = tag.toLowerCase(); //HACK: make components case-insensitive
this.$getNewContext(tag);
var oExt = this.$getLayoutNode(tag);
var node;
if (node = (aml || this).getAttributeNode("style"))
oExt.setAttribute("style", node.nodeValue);
//if (node = (aml || this).getAttributeNode("class"))
//this.$setStyleClass(oExt, (oldClass = node.nodeValue));
if (func)
func.call(this, oExt);
oExt = apf.insertHtmlNode(oExt, pNode);
oExt.host = this;
if (node = (aml || this).getAttributeNode("bgimage"))
oExt.style.backgroundImage = "url(" + apf.getAbsolutePath(
this.mediaPath, node.nodeValue) + ")";
if (!this.$baseCSSname)
this.$baseCSSname = oExt.className.trim().split(" ")[0];
return oExt;
};
// *** Focus *** //
this.$focus = function(){
if (!this.$ext)
return;
this.$setStyleClass(this.oFocus || this.$ext, this.$baseCSSname + "Focus");
};
this.$blur = function(){
if (this.renaming)
this.stopRename(null, true);
if (!this.$ext)
return;
this.$setStyleClass(this.oFocus || this.$ext, "", [this.$baseCSSname + "Focus"]);
};
this.$fixScrollBug = function(){
if (this.$int != this.$ext)
this.oFocus = this.$int;
else {
this.oFocus =
this.$int =
this.$ext.appendChild(document.createElement("div"));
this.$int.style.height = "100%";
this.$int.className = "focusbug"
}
};
// *** Caching *** //
/*
this.$setClearMessage = function(msg) {};
this.$updateClearMessage = function(){}
this.$removeClearMessage = function(){};*/
}).call(apf.Presentation.prototype = new apf.GuiElement());
apf.config.$inheritProperties["skinset"] = 1;
apf.__VALIDATION__ = 1 << 6;
//if checkequal then notnull = true
apf.validator = {
macro: {
//var temp
"pattern" : "value.match(",
"pattern_" : ")",
"custom" : "(",
"custom_" : ")",
"min" : "parseInt(value) >= ",
"max" : "parseInt(value) <= ",
"maxlength" : "value.toString().length <= ",
"minlength" : "value.toString().length >= ",
"notnull" : "value.toString().length > 0",
"checkequal" : "!(temp = ",
"checkequal_" : ").isValid() || temp.getValue() == value"
},
compile: function(options) {
var m = this.macro, s = ["var temp, valid = true; \
if (!validityState) \
validityState = new apf.validator.validityState(); "];
if (options.required) {
s.push("if (checkRequired && (!value || value.toString().trim().length == 0)) {\
validityState.$reset();\
validityState.valueMissing = true;\
valid = false;\
}")
}
s.push("validityState.$reset();\
if (value) {");
for (prop in options) {
if (!m[prop]) continue;
s.push("if (!(", m[prop], options[prop], m[prop + "_"] || "", ")){\
validityState.$set('", prop, "');\
valid = false;\
}");
}
s.push("};validityState.valid = valid; return validityState;");
return new Function('value', 'checkRequired', 'validityState', s.join(""));
}
};
/**
* Object containing information about the validation state. It contains
* properties that specify whether a certain validation was passed.
* Remarks:
* This is part of {@link http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#validitystatethe HTML 5 specification}.
*/
apf.validator.validityState = function(){
this.valueMissing = false,
this.typeMismatch = false,
this.patternMismatch = false,
this.tooLong = false,
this.rangeUnderflow = false,
this.rangeOverflow = false,
this.stepMismatch = false,
this.customError = false,
this.valid = true,
this.$reset = function(){
for (var prop in this) {
if (prop.substr(0,1) == "$")
continue;
this[prop] = false;
}
this.valid = true;
},
this.$set = function(type) {
switch (type) {
case "min" : this.rangeUnderflow = true; break;
case "max" : this.rangeOverflow = true; break;
case "minlength" : this.tooShort = true; break;
case "maxlength" : this.tooLong = true; break;
case "pattern" : this.patternMismatch = true; break;
case "datatype" : this.typeMismatch = true; break;
case "notnull" : this.typeMismatch = true; break;
case "checkequal" : this.typeMismatch = true; break;
}
}
};
/**
* All elements inheriting from this {@link term.baseclass baseclass} have validation support.
*
* #### Example
*
* ```xml, demo
*
*
*
* Number
*
* Name
*
* Message
*
*
*
* Validate
*
*
*
*
* ```
*
* @class apf.Validation
* @inherits apf.AmlElement
* @baseclass
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.5
*/
/**
* @event invalid Fires when this component goes into an invalid state.
*
*/
apf.Validation = function(){
this.$regbase = this.$regbase | apf.__VALIDATION__;
/**
* Checks if this element's value is valid.
*
* @param {Boolean} [checkRequired] Specifies whether this check also adheres to the `'required'` rule.
* @returns {Boolean} Specifies whether the value is valid
* @see apf.ValidationGroup
* @see element.submitform
*/
this.isValid = function(checkRequired) {
if (!this.$vOptions)
return true;
(this.$vOptions.isValid || (this.$vOptions.isValid
= apf.validator.compile(this.$vOptions))).call(this,
typeof this.getValue == "function" ? this.getValue(null, true) : null,
checkRequired, this.validityState ||
(this.validityState = new apf.validator.validityState()));
var valid = this.validityState.valid;
this.dispatchEvent(!valid ? "invalid" : "valid", this.validityState);
return valid;
};
/*
* @private
*/
this.setCustomValidity = function(message) {
//do stuff
}
/*
* @private
* @todo This method should also scroll the element into view
*/
this.showMe = function(){
var p = this.parentNode;
while (p) {
if (p.show)
p.show();
p = p.parentNode;
}
};
/**
* Puts this element in the error state, optionally showing the
* error box if this element is invalid.
*
* @method validate
* @param {Boolean} [ignoreReq] Specifies whether this element required check is turned on.
* @param {Boolean} [nosetError] Specifies whether the error box is displayed if this component does not validate.
* @param {Boolean} [force] Specifies whether this element is in the error state, and doesn't check if the element's value is invalid.
* @return {Boolean} Indicates whether the value is valid
* @see apf.ValidationGroup
*/
this.validate = function(ignoreReq, nosetError, force) {
//if (!this.$validgroup) return this.isValid();
if (force || !this.isValid(!ignoreReq) && !nosetError) {
this.setError();
return false;
}
else {
this.clearError();
return true;
}
};
/*
* @private
*/
this.setError = function(value) {
if (!this.$validgroup)
this.$propHandlers["validgroup"].call(this, "vg" + this.parentNode.$uniqueId);
var errBox = this.$validgroup.getErrorBox(this);
if (!this.$validgroup.allowMultipleErrors)
this.$validgroup.hideAllErrors();
errBox.setMessage(this.invalidmsg || value);
apf.setStyleClass(this.$ext, this.$baseCSSname + "Error");
this.showMe(); //@todo scroll refHtml into view
if (this.invalidmsg || value)
errBox.display(this);
if (apf.document.activeElement && apf.document.activeElement != this)
this.focus(null, {mouse:true}); //arguable...
};
/*
* @private
*/
this.clearError = function(value) {
if (this.$setStyleClass)
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Error"]);
if (this.$validgroup) {
var errBox = this.$validgroup.getErrorBox(null, true);
if (!errBox || errBox.host != this)
return;
errBox.hide();
}
};
this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
if (this.$validgroup)
this.$validgroup.unregister(this);
});
/**
*
* @attribute {Boolean} required Sets or gets whether a valid value for this element is required.
*/
/**
* @attribute {RegExp} pattern Sets or gets the pattern tested against the value of this element to determine it's validity.
*/
/**
* @attribute {String} datatype Sets or gets the datatype that the value of this element should adhere to. This can be any
* of a set of predefined types, or a simple type created by an XML Schema definition.
*
* Some possible values (all of which are [[String]]s) include:
* - `xsd:dateTime`
* - `xsd:time`
* - `xsd:date`
* - `xsd:gYearMonth`
* - `xsd:gYear`
* - `xsd:gMonthDay`
* - `xsd:gDay`
* - `xsd:gMonth`
* - `xsd:string`
* - `xsd:boolean`
* - `xsd:base64Binary`
* - `xsd:hexBinary`
* - `xsd:float`
* - `xsd:decimal`
* - `xsd:double`
* - `xsd:anyURI`
* - `xsd:integer`
* - `xsd:nonPositiveInteger`
* - `xsd:negativeInteger`
* - `xsd:long`
* - `xsd:int`
* - `xsd:short`
* - `xsd:byte`
* - `xsd:nonNegativeInteger`
* - `xsd:unsignedLong`
* - `xsd:unsignedInt`
* - `xsd:unsignedShort`
* - `xsd:unsignedByte`
* - `xsd:positiveInteger`
* - `apf:url`
* - `apf:website`
* - `apf:email`
* - `apf:creditcard`
* - `apf:expdate`
* - `apf:wechars`
* - `apf:phonenumber`
* - `apf:faxnumber`
* - `apf:mobile`
*/
/**
* @attribute {Number} min Sets or gets the minimal value for which the value of this element is valid.
*/
/**
* @attribute {Number} max Sets or gets the maximum value for which the value of this element is valid.
*/
/**
* @attribute {Number} minlength Sets or gets the minimal length allowed for the value of this element.
*/
/**
* @attribute {Number} maxlength Sets or gets the maximum length allowed for the value of this element.
*/
/**
* @attribute {Boolean} notnull Sets or gets whether the value is filled. This rule is checked realtime when the element loses the focus.
*/
/**
* @attribute {String} checkequal Sets or gets the id of the element to check if it has the same value as this element.
*/
/**
* @attribute {String} invalidmsg Sets or gets the message displayed when this element has an invalid value. Use a `;` character to seperate the title from the message.
*/
/**
* @attribute {String} validgroup Sets or gets the identifier for a group of items to be validated at the same time. This identifier can be new. It is inherited from a AML node upwards.
*/
/**
* @attribute {String} validtest Sets or gets the instruction on how to test for success. This attribute is generally used to check the value on the server.
*
* #### Example
*
* This example shows how to check the username on the server. In this case,
* `comm.loginCheck` is an async RPC function that checks the availability of the
* username. If it exists, it will return `0`--otherwise, it's `1`. The value variable
* contains the current value of the element (in this case the textbox). It
* can be used as a convenience variable.
*
* ```xml
* Username
*
* ```
*/
this.addEventListener("DOMNodeInsertedIntoDocument", function(e) {
//this.addEventListener(this.hasFeature(apf.__MULTISELECT__) ? "onafterselect" : "onafterchange", onafterchange);
/* Temp disabled, because I don't understand it (RLD)
this.addEventListener("beforechange", function(){
if (this.xmlRoot && apf.getBoundValue(this) === this.getValue())
return false;
});*/
// validgroup
if (!this.validgroup)
this.$setInheritedAttribute("validgroup");
});
//1 = force no bind rule, 2 = force bind rule
this.$attrExcludePropBind = apf.extend({
pattern: 1,
validtest: 3
}, this.$attrExcludePropBind);
this.$booleanProperties["required"] = true;
this.$supportedProperties.push("validgroup", "required", "datatype",
"pattern", "min", "max", "maxlength", "minlength", "validtest",
"notnull", "checkequal", "invalidmsg", "requiredmsg");
this.$fValidate = function(){
if (this.liveedit)
return;
if (!this.$validgroup)
this.validate(true);
else {
var errBox = this.$validgroup.getErrorBox(this);
if (!errBox.visible || errBox.host != this)
this.validate(true);
}
};
this.addEventListener("blur", this.$fValidate);
this.$propHandlers["validgroup"] = function(value) {
if (value) {
var vgroup;
if (typeof value != "string") {
this.$validgroup = value.name;
vgroup = value;
}
else {
vgroup = apf.nameserver.get("validgroup", value);
}
this.$validgroup = vgroup || new apf.ValidationGroup(value);
this.$validgroup.register(this);
/*
@todo What about children, when created after start
See button login action
*/
}
else {
this.$validgroup.unregister(this);
this.$validgroup = null;
}
};
this.$propHandlers["pattern"] = function(value, prop) {
if (value.substr(0, 1) != "/")
value = "/" + value + "/";
(this.$vOptions || (this.$vOptions = {}))[prop] = value;
delete this.$vOptions.isValid;
};
this.$propHandlers["required"] =
this.$propHandlers["custom"] =
this.$propHandlers["min"] =
this.$propHandlers["max"] =
this.$propHandlers["maxlength"] =
this.$propHandlers["minlength"] =
this.$propHandlers["notnull"] =
this.$propHandlers["checkequal"] = function(value, prop) {
(this.$vOptions || (this.$vOptions = {}))[prop] = value;
delete this.$vOptions.isValid;
};
//@todo rewrite this for apf3.0 - it should just execute a live markup
this.$propHandlers["validtest"] = function(value) {
var _self = this, rvCache = {};
/**
* Removes the validation cache created by the validtest rule.
*/
this.removeValidationCache = function(){
rvCache = {};
}
this.$checkRemoteValidation = function(){
var value = this.getValue();
if (typeof rvCache[value] == "boolean") return rvCache[value];
if (rvCache[value] == -1) return true;
rvCache[value] = -1;
apf.getData(this.validtest.toString(), {
xmlNode: this.xmlRoot,
value: this.getValue(),
callback: function(data, state, extra) {
if (state != apf.SUCCESS) {
if (state == apf.TIMEOUT && extra.retries < apf.maxHttpRetries)
return extra.tpModule.retry(extra.id);
else {
var commError = new Error(apf.formatErrorString(0, _self,
"Validating entry at remote source",
"Communication error: \n\n" + extra.message));
if (_self.dispatchEvent("error", apf.extend({
error: commError,
state: status
}, extra)) !== false)
throw commError;
return;
}
}
rvCache[value] = apf.isTrue(data);//instr[1] ? data == instr[1] : apf.isTrue(data);
if (!rvCache[value]){
if (!_self.hasFocus())
_self.setError();
}
else _self.clearError();
}
});
return true;
};
(this.$vOptions || (this.$vOptions = {})).custom = "apf.lookup(" + this.$uniqueId + ").$checkRemoteValidation()";
delete this.$vOptions.isValid;
};
};
apf.GuiElement.propHandlers["required"] =
apf.GuiElement.propHandlers["pattern"] =
apf.GuiElement.propHandlers["min"] =
apf.GuiElement.propHandlers["max"] =
apf.GuiElement.propHandlers["maxlength"] =
apf.GuiElement.propHandlers["minlength"] =
apf.GuiElement.propHandlers["notnull"] =
apf.GuiElement.propHandlers["checkequal"] =
apf.GuiElement.propHandlers["validtest"] = function(value, prop) {
this.implement(apf.Validation);
this.$propHandlers[prop].call(this, value, prop);
}
/**
* This object allows for a set of AML elements to be validated. An element that
* is not valid shows an errorbox.
*
* #### Example
*
* ```xml
*
* Phone number
*
*
* Password
*
*
* ```
*
* To check if the element has valid information entered, leaving the textbox
* (focussing another element) will trigger a check. Programmatically, a check
* can be done using the following code:
*
*
* ```javascript
* txtPhone.validate();
*
* // Or, use the html5 syntax
* txtPhone.checkValidity();
* ```
*
* To check for the entire group of elements, use the validation group. For only
* the first non-valid element the errorbox is shown. That element also receives
* focus.
*
* ```javascript
* vgForm.validate();
* ```
*
* @class apf.ValidationGroup
* @inherits apf.Class
* @default_private
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.9
*/
/**
* @event validation Fires when the validation group isn't validated.
*/
apf.ValidationGroup = function(name) {
this.$init();
this.childNodes = [];
if (name)
apf.setReference(name, this);
this.name = name || "validgroup" + this.$uniqueId;
apf.nameserver.register("validgroup", this.name, this);
};
(function(){
/**
* When set to true, only visible elements are validated.
* @type Boolean
*/
this.validateVisibleOnly = false;
/**
* When set to true, validation doesn't stop at the first invalid element.
* @type Boolean
*/
this.allowMultipleErrors = false;
/**
* Adds an AML element to this validation group.
* @param o {apf.AmlElement} The AML element to add
*/
this.register = function(o) {
if (o.hasFeature(apf.__VALIDATION__))
this.childNodes.push(o);
};
/**
* Removes a AML element from this validation group.
* @param o {apf.AmlElement} The AML element to remove
*/
this.unregister = function(o) {
this.childNodes.remove(o);
};
/**
* Returns a string representation of this object.
*/
this.toString = function(){
return "[APF Validation Group]";
};
//Shared among all validationgroups
var errbox;
/**
* Retrieves the {@link apf.errorbox} used for a specified element.
*
* @param {apf.AmlNode} o An AMLNode specifying the element for which the Errorbox should be found. If none is found, an Errorbox is created. Use the {@link apf.ValidationGroup.allowMultipleErrors} to influence when Errorboxes are created.
* @param {Boolean} no_create Boolean that specifies whether new Errorbox may be created when it doesn't exist already
* @return {apf.errorbox} The found (or created) Errorbox
*/
this.getErrorBox = function(o, no_create) {
if (this.allowMultipleErrors || !errbox && !no_create) {
errbox = new apf.errorbox();
errbox.$pHtmlNode = o.$ext.parentNode;
errbox.skinset = apf.getInheritedAttribute(o.parentNode, "skinset"); //@todo use skinset here. Has to be set in presentation
errbox.dispatchEvent("DOMNodeInsertedIntoDocument");
}
return errbox;
};
/**
* Hide all Errorboxes for the elements using this element as their validation group.
*
*/
this.hideAllErrors = function(){
if (errbox && errbox.host)
errbox.host.clearError();
};
function checkValidChildren(oParent, ignoreReq, nosetError) {
var found;
//Per Element
for (var v, i = 0; i < oParent.childNodes.length; i++) {
var oEl = oParent.childNodes[i];
if (!oEl)
continue;
if (!oEl.disabled
&& (!this.validateVisibleOnly && oEl.visible || !oEl.$ext || oEl.$ext.offsetHeight)
&& (oEl.hasFeature(apf.__VALIDATION__) && oEl.isValid && !oEl.isValid(!ignoreReq))) {
//|| !ignoreReq && oEl.required && (!(v = oEl.getValue()) || new String(v).trim().length == 0)
if (!nosetError) {
if (!found) {
oEl.validate(true, null, true);
found = true;
if (!this.allowMultipleErrors)
return true; //Added (again)
}
else if (oEl.errBox && oEl.errBox.host == oEl)
oEl.errBox.hide();
}
else if (!this.allowMultipleErrors)
return true;
}
if (oEl.canHaveChildren && oEl.childNodes.length) {
found = checkValidChildren.call(this, oEl, ignoreReq, nosetError) || found;
if (found && !this.allowMultipleErrors)
return true; //Added (again)
}
}
return found;
}
/**
*
* @inheritDoc apf.ValidationGroup.isValid
* @method
*/
this.validate =
/**
* Checks if (part of) the set of element's registered to this element are
* valid. When an element is found with an invalid value, the error state can
* be set for that element.
*
* @method isValid
* @param {Boolean} [ignoreReq] Specifies whether to adhere to the 'required' check.
* @param {Boolean} [nosetError Specifies whether to not set the error state of the element with an invalid value
* @param {apf.AmlElement} [page] The page for which the children will be checked. When not specified all elements of this validation group are checked.
* @return {Boolean} Specifies whether the checked elements are valid.
*/
this.isValid = function(ignoreReq, nosetError, page) {
var found = checkValidChildren.call(this, page || this, ignoreReq,
nosetError);
if (page) {
if (page.validation && !eval(page.validation)) {
alert(page.invalidmsg);
found = true;
}
}
//Global Rules
//
//if (!found)
//found = this.dispatchEvent("validation");
return !found;
};
}).call(apf.ValidationGroup.prototype = new apf.Class());
apf.config.$inheritProperties["validgroup"] = 1;
require("./lib/dropdown")(apf);
require("./lib/splitbox")(apf);
apf.__ALIGNMENT__ = 1 << 29;
/**
* Baseclass of an element that has one or two states and can be clicked on to
* trigger an action (_i.e._ {@link apf.button} or {@link apf.checkbox}).
*
* @class apf.BaseButton
* @baseclass
* @author Abe Ginner
* @version %I%, %G%
* @since 0.8
* @inherits apf.StandardBinding
*/
/**
* @event click Fires when the user presses a mouse button while over this element...and then lets the mousebutton go.
*/
apf.BaseButton = function(){
this.$init(true);
};
(function() {
this.implement(apf.ChildValue);
this.$refKeyDown = // Number of keys pressed.
this.$refMouseDown = 0; // Mouse button down?
this.$mouseOver = // Mouse hovering over the button?
this.$mouseLeft = false; // Has the mouse left the control since pressing the button.
// *** Properties and Attributes *** //
/**
* @attribute {String} background Sets or gets a multistate background. The arguments
* are seperated by pipes (`'|'`) and are in the order of:'imagefilename|mapdirection|nrofstates|imagesize'
*
* - The `mapdirection` argument may have the value of `'vertical'` or `'horizontal'`.
* - The `nrofstates` argument specifies the number of states the iconfile contains:
* - 1: normal
* - 2: normal, hover
* - 3: normal, hover, down
* - 4: normal, hover, down, disabled
* - The `imagesize` argument specifies how high or wide each icon is inside the
* map, depending on the `mapdirection` argument.
* {: #multiStateDoc}
*
* #### Example
*
* Here's a three state picture where each state is 16px high, vertically spaced:
*
* ```xml
* background="threestates.gif|vertical|3|16"
* ```
*/
this.$propHandlers["background"] = function(value) {
var oNode = this.$getLayoutNode("main", "background", this.$ext);
if (!oNode) return;
if (value) {
var b = value.split("|");
this.$background = b.concat(["vertical", 2, 16].slice(b.length - 1));
oNode.style.backgroundImage = "url(" + this.mediaPath + b[0] + ")";
oNode.style.backgroundRepeat = "no-repeat";
}
else {
oNode.style.backgroundImage = "";
oNode.style.backgroundRepeat = "";
this.$background = null;
}
};
// *** Keyboard Support *** //
this.addEventListener("keydown", function(e) {
var key = e.keyCode;
//var ctrlKey = e.ctrlKey; << UNUSED
//var shiftKey = e.shiftKey; << UNUSED
switch (key) {
case 13:
if (this.localName != "checkbox")
this.$ext.onmouseup(e.htmlEvent, true);
break;
case 32:
if (!e.htmlEvent.repeat) { // Only when first pressed, not on autorepeat.
this.$refKeyDown++;
this.$updateState(e.htmlEvent);
}
return false;
}
}, true);
this.addEventListener("keyup", function(e) {
var key = e.keyCode;
switch (key) {
case 32:
this.$refKeyDown--;
if (this.$refKeyDown < 0) {
this.$refKeyDown = 0;
return false;
}
if (this.$refKeyDown + this.$refMouseDown == 0 && !this.disabled)
this.$ext.onmouseup(e, true);
this.$updateState(e);
return false;
}
}, true);
// *** Private state handling methods *** //
this.states = {
"Out" : 1,
"Over" : 2,
"Down" : 3
};
this.$updateState = function(e, strEvent) {
if (e.reset) { //this.disabled ||
this.$refKeyDown = 0;
this.$refMouseDown = 0;
this.$mouseOver = false;
return false;
}
if (this.$refKeyDown > 0
|| (this.$refMouseDown > 0 && (this.$mouseOver || (this.$ext === e.currentTarget)))
|| (this.isBoolean && this.value)) {
this.$setState("Down", e, strEvent);
}
else if (this.$mouseOver) {
this.$setState("Over", e, strEvent);
}
else
this.$setState("Out", e, strEvent);
};
this.$setupEvents = function() {
if (this.editable)
return;
var _self = this;
this.$ext.onmousedown = function(e) {
e = e || window.event;
if (_self.$notfromext && (e.srcElement || e.target) == this)
return;
_self.$refMouseDown = 1;
_self.$mouseLeft = false;
if (_self.disabled)
return;
if (!apf.isIE) { // && (apf.isGecko || !_self.submenu) Causes a focus problem for menus
if (_self.value)
apf.stopEvent(e);
else
apf.cancelBubble(e);
}
_self.$updateState(e, "mousedown");
};
this.$ext.onmouseup = function(e, force) {
e = e || window.event;
//if (e) e.cancelBubble = true;
if (_self.disabled || !force && ((!_self.$mouseOver && (this !== e.currentTarget)) || !_self.$refMouseDown))
return;
_self.$refMouseDown = 0;
_self.$updateState(e, "mouseup");
// If this is coming from a mouse click, we shouldn't have left the button.
if (_self.disabled || (e && e.type == "click" && _self.$mouseLeft == true))
return false;
// If there are still buttons down, this is not a real click.
if (_self.$refMouseDown + _self.$refKeyDown)
return false;
if (_self.$clickHandler && _self.$clickHandler())
_self.$updateState (e || event, "click");
else
_self.dispatchEvent("click", {htmlEvent : e});
return false;
};
this.$ext.onmousemove = function(e) {
if ((!_self.$mouseOver || _self.$mouseOver == 2)) {
e = e || window.event;
if (_self.$notfromext && (e.srcElement || e.target) == this)
return;
_self.$mouseOver = true;
if (!_self.disabled)
_self.$updateState(e, "mouseover");
}
};
this.$ext.onmouseout = function(e) {
e = e || window.event;
//Check if the mouse out is meant for us
var tEl = e.explicitOriginalTarget || e.toElement;
if (apf.isChildOf(this, tEl)) //this == tEl ||
return;
_self.$mouseOver = false;
_self.$refMouseDown = 0;
_self.$mouseLeft = true;
if (!_self.disabled)
_self.$updateState(e, "mouseout");
};
if (apf.hasClickFastBug)
this.$ext.ondblclick = this.$ext.onmouseup;
};
this.$doBgSwitch = function(nr) {
if (this.background && (this.$background[2] >= nr || nr == 4)) {
if (nr == 4)
nr = this.$background[2] + 1;
var strBG = this.$background[1] == "vertical"
? "0 -" + (parseInt(this.$background[3]) * (nr - 1)) + "px"
: "-" + (parseInt(this.$background[3]) * (nr - 1)) + "px 0";
this.$getLayoutNode("main", "background",
this.$ext).style.backgroundPosition = strBG;
}
};
// *** Focus Handling *** //
this.$focus = function(){
if (!this.$ext)
return;
this.$setStyleClass(this.$ext, this.$baseCSSname + "Focus");
};
this.$blur = function(e) {
if (!this.$ext)
return; //FIREFOX BUG!
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Focus"]);
/*this.$refKeyDown = 0;
this.$refMouseDown = 0;
this.$mouseLeft = true;*/
/*if (this.submenu) {
if (this.value) {
this.$setState("Down", {}, "mousedown");
this.$hideMenu();
}
}*/
if (e)
this.$updateState({});//, "onblur"
};
this.addEventListener("prop.disabled", function(e) {
this.$refKeyDown =
this.$refMouseDown = 0;
//this.$mouseOver =
//this.$mouseLeft = false;
});
/*** Clearing potential memory leaks ****/
this.$destroy = function(skinChange) {
if (!skinChange && this.$ext) {
this.$ext.onmousedown = this.$ext.onmouseup = this.$ext.onmouseover =
this.$ext.onmouseout = this.$ext.onclick = this.$ext.ondblclick = null;
}
};
}).call(apf.BaseButton.prototype = new apf.StandardBinding());
/**
* Baseclass of a simple element. These are usually displaying elements
* (_i.e._ {@link apf.label}, {@link apf.img})
*
* @class apf.BaseSimple
* @baseclass
*
* @inherits apf.StandardBinding
* @inherits apf.DataAction
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*/
apf.BaseSimple = function(){
this.$init(true);
};
(function() {
this.implement(apf.DataAction);
this.getValue = function(){
return this.value;
};
}).call(apf.BaseSimple.prototype = new apf.StandardBinding());
/**
* The base class for state buttons.
*
* @class apf.BaseStateButtons
* @baseclass
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*/
apf.BaseStateButtons = function(){
this.state = "normal";
this.edit = false;
var actions = {
"min" : ["minimized", "minimize", "restore"],
"max" : ["maximized", "maximize", "restore"],
"edit" : ["edit", "edit", "closeedit"],
"close" : ["closed", "close", "show"]
};
this.$lastheight = null;
this.$lastpos = null;
this.$lastState = {"normal":true};
this.$booleanProperties["animate"] = true;
this.$supportedProperties.push("buttons", "animate", "state");
/**
* Close the window. It can be reopened by using {@link apf.GuiElement.show}
* @chainable
*/
this.close = function(){ // @todo show should unset closed
this.setProperty("state", this.state.split("|")
.pushUnique("closed").join("|"), false, true);
return this;
};
/**
* Minimize the window. The window will become the height of the title of
* the parent window.
* @chainable
*/
this.minimize = function(){
this.setProperty("state", this.state.split("|")
.remove("maximized")
.remove("normal")
.pushUnique("minimized").join("|"), false, true);
return this;
};
/**
* Maximize the window. The window will become the width and height of the
* browser window.
* @chainable
*/
this.maximize = function(){
this.setProperty("state", this.state.split("|")
.remove("minimized")
.remove("normal")
.pushUnique("maximized").join("|"), false, true);
return this;
};
/**
* Restore the size of the window. The window will become the width and
* height it had before it was minimized or maximized.
* @chainable
*/
this.restore = function(){
this.setProperty("state", this.state.split("|")
.remove("minimized")
.remove("maximized")
.pushUnique("normal").join("|"), false, true);
return this;
};
/**
* Set the window into edit state. The configuration panel is shown.
* @chainable
*/
this.edit = function(value) {
this.setProperty("state", this.state.split("|")
.pushUnique("edit").join("|"), false, true);
return this;
};
/**
* Removes the edit state of this window. The configuration panel is hidden.
* @chainable
*/
this.closeedit = function(value) {
this.setProperty("state", this.state.split("|")
.remove("edit").join("|"), false, true);
return this;
};
this.$toggle = function(type) {
var c = actions[type][0];
this[actions[type][this.state.indexOf(c) > -1 ? 2 : 1]]();
};
this.$propHandlers["refparent"] = function(value) {
if (typeof value == "string")
this.$refParent = self[value] && self[value].$ext || document.getElementById(value);
else this.$refParent = value;
}
this.$propHandlers["maxconf"] = function(value) {
this.$maxconf = value.splitSafe(",");
}
/**
* @attribute {String} state Sets or gets the state of the window. The state can be a
* combination of multiple states, seperated by a pipe (`'|'`) character.
*
* The possible values include:
*
* `"normal"`: The window has its normal size and position. This is the default value.
* `"minimized"`: The window is minimized.
* `"maximized"`: The window is maximized.
* `"edit"`: The window is in the edit state.
* `"closed"`: The window is closed.
*/
this.$propHandlers["state"] = function(value, prop, force, reenter, noanim) {
var _self = this;
if (!this.$amlLoaded) { //@todo I still think this is weird and should not be needed
apf.queue.add("state" + this.$uniqueId, function(){
_self.$propHandlers["state"].call(_self, value, prop, force, reenter, noanim);
});
return;
}
if (value == 0)
value = "normal";
var i, pNode, position, l, t,
o = {},
s = value.split("|"),
lastState = this.$lastState,
styleClass = [];
for (i = 0; i < s.length; i++)
o[s[i]] = true;
o.value = value;
if (!o.maximized && !o.minimized)
o.normal = true;
if (!reenter && this.dispatchEvent("beforestatechange", {
from: lastState,
to: o}) === false) {
this.state = lastState.value;
return false;
}
//Closed state
if (o.closed == this.visible) {//change detected
this.setProperty("visible", !o["closed"], false, true);
//@todo difference is, we're not clearing the other states, check the docking example
}
//Restore state
if (o.normal != lastState.normal
|| !o.normal && (o.minimized != lastState.minimized
|| o.maximized != lastState.maximized)) {
if (this.$lastheight != null) // this.aData && this.aData.hidden == 3 ??
this.$ext.style.height = this.$lastheight;//(this.$lastheight - apf.getHeightDiff(this.$ext)) + "px";
if (this.$lastpos) {
apf.plane.hide(this.$uniqueId);
if (this.animate && !noanim) {
//Pre remove paused event because of not having onresize
//if (apf.hasSingleRszEvent)
//delete apf.layout.onresize[apf.layout.getHtmlId(this.$pHtmlNode)];
var htmlNode = this.$ext;
position = apf.getStyle(htmlNode, "position");
if (position != "absolute") {
l = parseInt(apf.getStyle(htmlNode, "left")) || 0;
t = parseInt(apf.getStyle(htmlNode, "top")) || 0;
}
else {
l = htmlNode.offsetLeft;
t = htmlNode.offsetTop;
}
this.animstate = 1;
apf.tween.multi(htmlNode, {
steps: 15,
anim: apf.tween.easeInOutCubic,
interval: 10,
tweens: [
{type: "left", from: l, to: this.$lastpos.px[0]},
{type: "top", from: t, to: this.$lastpos.px[1]},
{type: "width", from: this.$ext.offsetWidth,
to: this.$lastpos.px[2]},
{type: "height", from: this.$ext.offsetHeight,
to: this.$lastpos.px[3]}
],
oneach: function(){
if (apf.hasSingleRszEvent)
apf.layout.forceResize(_self.$int);
},
onfinish: function(){
_self.$lastpos.parentNode.insertBefore(_self.$ext, _self.$lastpos.beforeNode);
if (_self.$placeHolder)
_self.$placeHolder.parentNode.removeChild(_self.$placeHolder);
_self.$propHandlers["state"].call(_self, value, null,
null, true, true);
}
});
return;
}
else if (!this.animate) {
apf.plane.hide(this.$uniqueId, true);
_self.$lastpos.parentNode.insertBefore(_self.$ext, _self.$lastpos.beforeNode);
if (_self.$placeHolder)
_self.$placeHolder.parentNode.removeChild(_self.$placeHolder);
}
this.$ext.style.position = this.$lastpos.pos;
this.$ext.style.left = this.$lastpos.css[0];
this.$ext.style.top = this.$lastpos.css[1];
this.$ext.style.width = this.$lastpos.css[2];
this.$ext.style.height = this.$lastpos.css[3];
pNode = this.$lastpos.parentNode;
pNode.style.width = this.$lastpos.parent[0];
pNode.style.height = this.$lastpos.parent[1];
pNode.style.overflow = this.$lastpos.parent[2];
}
if (this.aData && this.aData.restore)
this.aData.restore();
if (apf.layout)
apf.layout.play(this.$pHtmlNode);
this.$lastheight = this.$lastpos = null;
if (o.normal)
styleClass.push("",
this.$baseCSSname + "Max",
this.$baseCSSname + "Min");
}
if (o.minimized != lastState.minimized) {
if (o.minimized) {
styleClass.unshift(
this.$baseCSSname + "Min",
this.$baseCSSname + "Max",
this.$baseCSSname + "Edit");
if (this.aData && this.aData.minimize)
this.aData.minimize(this.collapsedHeight);
if (!this.aData || !this.aData.minimize) {
this.$lastheight = this.$ext.style.height; //apf.getStyle(this.$ext, "height");//this.$ext.offsetHeight;
this.$ext.style.height = Math.max(0, this.collapsedHeight
- apf.getHeightDiff(this.$ext)) + "px";
}
if (this.hasFocus())
apf.window.moveNext(null, this, true);
//else if(apf.document.activeElement)
//apf.document.activeElement.$focus({mouse: true});
}
else {
styleClass.push(this.$baseCSSname + "Min");
$setTimeout(function(){
apf.window.$focusLast(_self);
});
}
}
if (o.maximized != lastState.maximized) {
if (o.maximized) {
styleClass.unshift(
this.$baseCSSname + "Max",
this.$baseCSSname + "Min",
this.$baseCSSname + "Edit");
pNode = this.$refParent;
if (!pNode)
pNode = (this.$ext.offsetParent == document.body
? document.documentElement
: this.$ext.parentNode);
this.animstate = 0;
var hasAnimated = false, htmlNode = this.$ext;
var position = apf.getStyle(htmlNode, "position");
if (position == "absolute") {
pNode.style.overflow = "hidden";
l = htmlNode.offsetLeft;
t = htmlNode.offsetTop;
}
else {
var pos = apf.getAbsolutePosition(htmlNode); //pNode
l = pos[0];//parseInt(apf.getStyle(htmlNode, "left")) || 0;
t = pos[1];//parseInt(apf.getStyle(htmlNode, "top")) || 0;
}
this.$lastpos = {
css: [this.$ext.style.left, this.$ext.style.top,
this.$ext.style.width, this.$ext.style.height,
this.$ext.style.margin, this.$ext.style.zIndex],
px: [l, t, this.$ext.offsetWidth,
this.$ext.offsetHeight],
parent: [pNode.style.width, pNode.style.height,
pNode.style.overflow],
pos: htmlNode.style.position,
parentNode: pNode,
beforeNode: this.$ext.nextSibling
};
if (this.parentNode.$layout) {
if (!this.$placeHolder)
this.$placeHolder = document.createElement("div");
this.$placeHolder.style.position = this.$lastpos.pos;
this.$placeHolder.style.left = this.$lastpos.css[0];
this.$placeHolder.style.top = this.$lastpos.css[1];
this.$placeHolder.style.width = this.$lastpos.px[2] + "px";
this.$placeHolder.style.height = this.$lastpos.px[3] + "px";
this.$placeHolder.style.margin = this.$lastpos.css[4];
this.$placeHolder.style.zIndex = this.$lastpos.css[5];
this.$pHtmlNode.insertBefore(this.$placeHolder, this.$ext);
htmlNode.style.position = "absolute";
}
document.body.appendChild(htmlNode);
htmlNode.style.left = l + "px";
htmlNode.style.top = t + "px";
function setMax(){
//While animating dont execute this function
if (_self.animstate)
return;
var w, h, pos, box, pDiff;
if (_self.maxconf) {
w = _self.$maxconf[0];
h = _self.$maxconf[1];
pos = [_self.$maxconf[2] == "center"
? (apf.getWindowWidth() - w)/2
: _self.$maxconf[2],
_self.$maxconf[3] == "center"
? (apf.getWindowHeight() - h)/3
: _self.$maxconf[3]];
}
else {
w = !apf.isIE && pNode == document.documentElement
? window.innerWidth
: pNode.offsetWidth,
h = !apf.isIE && pNode == document.documentElement
? window.innerHeight
: pNode.offsetHeight;
}
if (!pos) {
pos = pNode != htmlNode.offsetParent
? apf.getAbsolutePosition(pNode, htmlNode.offsetParent)
: [0, 0];
}
if (position != "absolute") {
var diff = apf.getDiff(pNode);
w -= diff[0];
h -= diff[0];
}
box = _self.$refParent ? [0,0,0,0] : marginBox;
pDiff = apf.getDiff(pNode);
pNode.style.width = (pNode.offsetWidth - pDiff[0]) + "px";
pNode.style.height = (pNode.offsetHeight - pDiff[1]) + "px";
if (!hasAnimated && _self.$maxconf && _self.$maxconf[4])
apf.plane.show(htmlNode, false, null, null, {
color: _self.$maxconf[4],
opacity: _self.$maxconf[5],
animate: _self.animate,
protect: _self.$uniqueId
});
if (_self.animate && !hasAnimated) {
_self.animstate = 1;
hasAnimated = true;
apf.tween.multi(htmlNode, {
steps: 15,
anim: apf.tween.easeInOutCubic,
interval: 10,
tweens: [
{type: "left", from: l, to: pos[0] - box[3]},
{type: "top", from: t, to: pos[1] - box[0]},
{type: "width", from: _self.$lastpos.px[2],
to: (w + box[1] + box[3] - apf.getWidthDiff(_self.$ext))},
{type: "height", from: _self.$lastpos.px[3],
to: (h + box[0] + box[2] - apf.getHeightDiff(_self.$ext))}
],
oneach: function(){
if (apf.hasSingleRszEvent)
apf.layout.forceResize(_self.$int);
},
onfinish: function(){
_self.animstate = 0;
_self.dispatchEvent("afterstatechange", {
from: lastState,
to: o
});
if (apf.hasSingleRszEvent)
apf.layout.forceResize(_self.$int);
}
});
}
else if (!_self.animstate) {
htmlNode.style.left = (pos[0] - box[3]) + "px";
htmlNode.style.top = (pos[1] - box[0]) + "px";
var diff = apf.getDiff(_self.$ext);
htmlNode.style.width = (w
- diff[0] + box[1] + box[3]) + "px";
htmlNode.style.height = (h
- diff[1] + box[0] + box[2]) + "px";
}
}
if (apf.layout)
apf.layout.pause(this.$pHtmlNode, setMax);
}
else {
styleClass.push(this.$baseCSSname + "Max");
}
}
if (o.edit != lastState.edit) {
if (o.edit) {
styleClass.unshift(
this.$baseCSSname + "Edit",
this.$baseCSSname + "Max",
this.$baseCSSname + "Min");
if (this.btnedit)
oButtons.edit.innerHTML = "close"; //hack
this.dispatchEvent('editstart');
}
else {
if (this.dispatchEvent('editstop') === false)
return false;
styleClass.push(this.$baseCSSname + "Edit");
if (styleClass.length == 1)
styleClass.unshift("");
if (this.btnedit)
oButtons.edit.innerHTML = "edit"; //hack
}
}
if (styleClass.length || o.closed != lastState.closed) {
if (styleClass.length)
this.$setStyleClass(this.$ext, styleClass.shift(), styleClass);
if (o.edit) { //@todo apf3.0
this.dispatchEvent("prop.visible", {value:true});
if (_self.oSettings)
apf.layout.forceResize(_self.oSettings);
}
//@todo research why this is not symmetrical
if (!o.maximized || !this.animate || lastState.maximized && _self.animate) {
_self.dispatchEvent("afterstatechange", {
from: lastState,
to: o});
}
this.$lastState = o;
if (this.aData && !o.maximized) { //@todo is this the most optimal position?
this.$purgeAlignment();
}
if (!this.animate && apf.hasSingleRszEvent && apf.layout)
apf.layout.forceResize(_self.$int);
}
};
var marginBox, hordiff, verdiff, oButtons = {}
/**
* @attribute {String} buttons Sets or gets the buttons that the window displays. This
* can be multiple values seperated by a pipe (`'|'`) character.
*
* The possible values include:
*
* `"min"`: The button that minimizes the window.
* `"max"`: The button that maximizes the window.
* `"close"`: The button that closes the window.
* `"edit"`: The button that puts the window into the edit state.
*/
this.$propHandlers["buttons"] = function(value) {
if (!this.$hasLayoutNode("button"))
return;
var buttons = value && (value = value.replace(/(\|)\||\|$/, "$1")).split("|") || [],
nodes = this.$buttons.childNodes,
re = value && new RegExp("(" + value + ")"),
found = {},
idleNodes = [];
//Check if we can 'remove' buttons
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].nodeType != 1 || nodes[i].tagName != "DIV") //@todo temp hack
continue;
if (nodes[i].getAttribute("button") && (!value
|| !nodes[i].className || !nodes[i].className.match(re))) {
nodes[i].style.display = "none";
this.$setStyleClass(nodes[i], "", ["min", "max", "close", "edit"]);
idleNodes.push(nodes[i]);
}
else {
found[RegExp.$1] = nodes[i];
}
}
//Create new buttons if needed
for (i = 0; i < buttons.length; i++) {
if (!buttons[i])
continue;
if (found[buttons[i]]) {
this.$buttons.insertBefore(found[buttons[i]], this.$buttons.firstChild);
continue;
}
var btn = idleNodes.pop();
if (!btn) {
this.$getNewContext("button");
btn = this.$getLayoutNode("button");
btn.setAttribute("button", "button");
setButtonEvents.call(this, btn);
btn = apf.insertHtmlNode(btn, this.$buttons);
}
this.$setStyleClass(btn, buttons[i], ["min", "max", "close", "edit"]);
btn.onclick = new Function("apf.lookup(" + this.$uniqueId + ").$toggle('"
+ buttons[i] + "')");
btn.style.display = "block";
oButtons[buttons[i]] = btn;
this.$buttons.insertBefore(btn, this.$buttons.firstChild);
}
marginBox = apf.getBox(apf.getStyle(this.$ext, "borderWidth"));
};
function setButtonEvents(btn) {
//@todo can this cancelBubble just go?
//event.cancelBubble = true; \
btn.setAttribute("onmousedown",
"var o = apf.all[" + this.$uniqueId + "];\
o.$setStyleClass(this, 'down', null, true);\
apf.cancelBubble(event, o); \
var o = apf.findHost(this).$ext;\
if (o.onmousedown) o.onmousedown(event);\
apf.cancelBubble(event, o);\
apf.window.$mousedown(event);");
btn.setAttribute("onmouseup",
"var o = apf.all[" + this.$uniqueId + "];\
o.$setStyleClass(this, '', ['down'], true);");
btn.setAttribute("onmouseover",
"var o = apf.all[" + this.$uniqueId + "];\
o.$setStyleClass(this, 'hover', null, true);");
btn.setAttribute("onmouseout",
"var o = apf.all[" + this.$uniqueId + "];\
o.$setStyleClass(this, '', ['hover', 'down'], true);");
btn.setAttribute("ondblclick", "apf.stopPropagation(event);");
}
this.$initButtons = function(oExt) {
this.animate = apf.enableAnim;
this.collapsedHeight = this.$getOption("Main", "collapsed-height");
var oButtons = this.$getLayoutNode("main", "buttons", oExt);
if (!oButtons || apf.isIphone || !this.getAttribute("buttons")
|| !this.$hasLayoutNode("button"))
return;
var len = (this.getAttribute("buttons") || "").split("|").length;
for (var btn, i = 0; i < len; i++) {
this.$getNewContext("button");
btn = oButtons.appendChild(this.$getLayoutNode("button"));
btn.setAttribute("button", "button");
setButtonEvents.call(this, btn);
}
};
this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
for (var name in oButtons) {
oButtons[name].onclick = null;
}
});
};
apf.__DELAYEDRENDER__ = 1 << 11
/**
* All elements inheriting from this {@link term.baseclass baseclass} have delayed
* rendering features.
*
* Any element that is (partially) hidden at startup has the
* possibility to delay rendering its childNodes by setting `render="runtime"` on
* the element. These elements include `window`, `tab`, `pages`, `form` and c`ontainer`.
* For instance, a tab page in a container is initally hidden and does not
* need to be rendered. When the tab button is pressed to activate the page,
* the page is rendered and then displayed. This can dramatically decrease
* the startup time of the application.
*
* #### Example
*
* In this example the button isn't rendered until the advanced tab becomes active.
*
* ```xml
*
*
* ...
*
*
* OK
*
*
* ```
* @class apf.DelayedRender
* @baseclass
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8.9
*/
/**
* @event beforerender Fires before elements are rendered. Use this event to display a loader.
* @cancelable Prevents rendering of the childNodes
*/
/**
* @event afterrender Fires after elements are rendered. Use this event to hide a loader.
*
*/
/**
* @attribute {String} render Sets or gets when the contents of this element is rendered.
*
* Possible values include:
*
* - init: elements are rendered during the initialization of the application.
* - runtime: elements are rendered when the user requests them.
*/
/**
* @attribute {Boolean} use-render-delay Sets or gets whether there's a short delay between showing this element and rendering its contents.
*
* If `true`, the elements are rendered immediately. Otherwise, there is a delay between showing this element and the actual rendering,
* allowing the browsers' render engine to draw (for instance, a loader).
*
*/
apf.DelayedRender = function(){
this.$regbase = this.$regbase | apf.__DELAYEDRENDER__;
this.$rendered = false;
/*
* Renders the children of this element.
*
* @param {Boolean} [usedelay] Specifies whether a delay is added between calling
* this function and the actual rendering. This allows the browsers'
* render engine to draw (for instance a loader).
*/
this.$render = function(usedelay) {
if (this.$rendered)
return;
if (this.dispatchEvent("beforerender") === false)
return;
if (this["render-delay"] || usedelay)
$setTimeout("apf.lookup(" + this.$uniqueId + ").$renderparse()", 10);
else
this.$renderparse();
};
this.$renderparse = function(){
if (this.$rendered)
return;
// Hide render pass from sight for inner callstack
// redrawing browsers like firefox
this.$ext.style.visibility = "hidden";
var domParser = this.ownerDocument.$domParser;
domParser.parseFromXml(this.$aml, {
amlNode: this,
doc: this.ownerDocument,
//nodelay : true,
delayedRender: true
});
domParser.$continueParsing(this);
this.$rendered = true;
this.dispatchEvent("afterrender");
this.addEventListener("$event.afterrender", function(cb) {
cb.call(this);
});
this.$ext.style.visibility = "";
};
/*var _self = this;
if (apf.window.vManager.check(this, "delayedrender", function(){
_self.$render();
})) this.$render();*/
var f;
this.addEventListener("prop.visible", f = function(){
if (arguments[0].value) {
this.$render();
this.removeEventListener("prop.visible", f);
}
});
};
apf.GuiElement.propHandlers["render"] = function(value) {
if (!this.hasFeature(apf.__DELAYEDRENDER__) && value == "runtime") {
this.implement(apf.DelayedRender);
if (this.localName != "page") {
this.visible = false;
this.$ext.style.display = "none";
}
if (typeof this["render-delay"] == "undefined")
this.$setInheritedAttribute("render-delay");
}
};
apf.config.$inheritProperties["render-delay"] = 1;
apf.__DRAGDROP__ = 1 << 5;
/**
* All elements inheriting from this {@link term.baseclass baseclass} have drag & drop
* features.
*
* This baseclass operates on the bound data of this element.
* When a rendered item is dragged and dropped, the bound data is moved or
* copied from one element to another, or to the same element but at a different
* position. This is possible because the rendered item has a
* {@link term.smartbinding bidirectional connection} to the data. Drag & drop can
* be turned on with a simple boolean, or by specifying detailed rules to set
* which data can be dragged and dropped and where.
*
*
* #### Example
*
* This is a simple example, enabling drag & drop for a list:
*
* ```xml
*
* ```
*
*
* #### Example
*
* This example shows a smartbinding that represents files and folders. It uses a
* {@link term.datainstruction data instruction} to communicate to the webdav
* server when an item is copied or moved.
*
* ```xml
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* ```
*
* #### Example
*
* This example shows a small mail application. The tree element displays a root
* node, accounts and folders in a tree. The datagrid contains the mails. This
* rule specifies which data nodes can be dropped where. Folders can be dropped
* in folders and accounts. Mails can be dropped in folders.
*
* ```xml
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*```
*
* @class apf.DragDrop
* @baseclass
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.5
* @define dragdrop
* @allowchild drop, drag
* @define drag
*/
/**
* @event dragdata Fires before a drag & drop operation is started to determine the data that is dragged.
* @param {Object} e The standard event object. It contains the following property:
* - `data` ([[XMLElement]]): The default data for the drag & drop operation
*/
/**
* @event dragstart Fires before a drag operation is started.
* @cancelable Prevents the drag operation to start.
* @param {Object} e The standard event object. It contains the following properties:
* - `data` ([[XMLElement]]): The data for the drag & drop operation
* - `selection` ([[XMLElement]]): The selection at the start of the drag operation
* - `indicator` ([[HTMLElement]]): The HTML element that is shown while dragging the data
* - `host` ([[apf.AmlElement]]): The AML source element.
*/
/**
* @event dragover Fires when the users drags over this AML element.
* @cancelable Prevents the possibility to drop.
* @param {Object} e The standard event object. It contains the following properties:
* {XMLElement} data The data for the drag & drop operation
* {XMLElement} selection The selection at the start of the drag operation
* {HTMLElement} indicator The HTML element that is shown while dragging the data
* {apf.AmlElement} host the AML source element.
*/
/**
* @event dragout Fires when the user moves away from this AML element.
* @param {Object} e The standard event object. It contains the following properties:
* {XMLElement} data the data for the drag & drop operation
* {XMLElement} selection the selection at the start of the drag operation
* {HTMLElement} indicator the HTML element that is shown while dragging the data
* {apf.AmlElement} host the aml source element.
*/
/**
* @event dragdrop Fires when the user drops an item on this aml element.
* @cancelable Prevents the possibility to drop.
* @param {Object} e The standard event object. It contains the following properties:
* {XMLElement} data The data for the drag & drop operation
* {XMLElement} selection The selection at the start of the drag operation
* {HTMLElement} indicator The html element that is shown while dragging the data
* {apf.AmlElement} host The AML source element.
* {Boolean} candrop Specifies whether the data can be inserted at the point hovered over by the user
*
*
*/
/**
* @attribute {String} match An XPath statement querying the
* {@link term.datanode data node} that is
* dragged. If the query matches a node it
* is allowed to be dropped. The XPath is
* automatically prefixed by `'self::'`.
*/
/**
* @attribute {String} copy A JavaScript expression that determines
* whether the dragged element is a copy or
* a move. Use event.ctrlKey to use the Ctrl
* key to determine whether the element is copied.
*
*/
/**
* @attribute {String} match An XPath statement querying the
* {@link term.datanode data node} that is
* dragged. If the query matches a node it
* is allowed to be dropped. The XPath is
* automatically prefixed by `'self::'`.
*/
/**
* @attribute {String} target An XPath statement determining the new
* parent of the dropped {@link term.datanode data node}.
* The XPath is automatically prefixed by `'self::'`.
*/
/**
* @attribute {String} action The action to perform when the
* {@link term.datanode data node} is inserted.
* The possible values include:
*
* - `tree-append`: Appends the {@link term.datanode data node} to the element its dropped on.
* - `list-append`: Appends the {@link term.datanode data node} to the root element of this element.
* - `insert-before`: Inserts the {@link term.datanode data node} before the elements its dropped on.
*/
/**
* @attribute {String} copy A JavaScript expression that determines
* whether the drop is a copy or a move.
* Use event.ctrlKey to use the [[keys: Ctrl]] key to
* determine whether the element is copied.
*/
apf.DragDrop = function(){
this.$regbase = this.$regbase | apf.__DRAGDROP__;
this.$dragInited = false;
/* **********************
Actions
***********************/
/**
* Copies a {@link term.datanode data node} to the bound data of this element.
*
* @action
* @param {XMLElement} xmlNode The {@link term.datanode data node} which is copied.
* @param {XMLElement} [pNode] The new parent element of the copied
* {@link term.datanode data node}. If none is
* specified the root element of the data
* loaded in this element is used.
* @param {XMLElement} [beforeNode] The position where the {@link term.datanode data node}
* is inserted.
*/
this.copy = function(nodeList, pNode, beforeNode, isMove) {
if (nodeList.nodeType)
nodeList = [nodeList];
var exec,
changes = [],
i = 0,
l = nodeList.length;
for (; i < l; i++) {
changes.push({
action: isMove ? "moveNode" : "appendChild",
args: [pNode, isMove
? nodeList[i]
: nodeList[i] = nodeList[i].cloneNode(true), beforeNode]
});
}
if (this.$actions[(isMove ? "movegroup" : "copygroup")]) {
exec = this.$executeAction("multicall", changes,
(isMove ? "movegroup" : "copygroup"), nodeList[0]);
}
else {
exec = this.$executeAction("multicall", changes,
(isMove ? "move" : "copy"), nodeList[0], null, null,
nodeList.length > 1 ? nodeList : null);
}
if (exec !== false)
return nodeList;
return false;
};
/**
* Moves a {@link term.datanode data node} to the bound data of this element.
*
* @action
* @param {XMLElement} xmlNode The {@link term.datanode data node} which is copied.
* @param {XMLElement} [pNode] The new parent element of the moved
* {@link term.datanode data node}. If none
* specified the root element of the data
* loaded in this element is used.
* @param {XMLElement} [beforeNode] The position where the
* {@link term.datanode data node} is inserted.
*/
this.move = function(nodeList, pNode, beforeNode) {
return this.copy(nodeList, pNode, beforeNode, true);
};
/**
* Determines whether the user is allowed to drag the passed
* {@link term.datanode data node}.
*
* For instance, imagine a mail application with a root
* node, accounts and folders in a tree, and mails in a datagrid. The rules
* would specify you can drag & drop folders within an account, and emails between
* folders, but not on accounts or the root.
*
* @param {XMLElement} dataNode The {@link term.datanode data node} subject to the test.
* @return {Boolean} The result of the test
* @see apf.DragDrop.isDragAllowed
*/
this.isDragAllowed = function(x, data) {
if (!this.dragroot && this.xmlRoot.firstChild == x[0])
return false;
if (this.disabled || !x || !x.length || !x[0])
return false;
if (this.drag || this.dragcopy) {
if (data)
data.merge(x);
return true;
}
/*var rules = this.$bindings["drag"]
|| this.$attrBindings && this.$attrBindings["drag"];
if (!rules || !rules.length)
return false;*/
var d,
ruleList = [],
j = 0,
l = x.length;
for (; j < l; j++) {
d = this.$getDataNode("drag", x[j], null, ruleList);
if (!d) return false; //It's all or nothing
if (data)
data.push(d);
}
return ruleList.length ? ruleList : false;
};
/**
* Determines whether the user is allowed to drop the passed
* {@link term.datanode data node}.
*
* For instance, imagine a mail application with a root
* node, accounts and folders in a tree, and mails in a datagrid. The rules
* would specify you can drag & drop folders within an account, and emails between
* folders, but not on accounts or the root.
*
* @param {XMLElement} dataNode The {@link term.datanode data node} subject
* to the test.
* @param {XMLElement} target The {@link term.datanode data node} on which
* the dragged data node is dropped.
* @return {Boolean} The result of the test
* @see apf.DragDrop.isDragAllowed
*/
this.isDropAllowed = function(x, target) {
if (this.disabled || !x || !x.length || !target) //!x[0] ???
return false;
if (!this.dragroot == false && this.xmlRoot.firstChild == x[0])
return false;
for (var i = x.length - 1; i >= 0; i--)
if (apf.isChildOf(x[i], target, true))
return false;
var data, tgt, hasDropRule = this.$attrBindings && this.$attrBindings["drop"];
if (this.drop && (!hasDropRule || hasDropRule.value == "true")) {
this.$setDynamicProperty("drop", this.hasFeature(apf.__MULTISELECT__)
? "[" + this.each + "]"
: "[node()]"); //@todo apf3.0 make sure each is without {}
hasDropRule = true;
}
if (hasDropRule) {
for (var j = 0, l = x.length; j < l; j++) {
data = this.$getDataNode("drop", x[j]);
if (!data)
break;
}
if (j == l && target && !apf.isChildOf(data, target, true))
return [target, null];
}
var rules = this.$bindings["drop"];
if (!rules || !rules.length)
return false;
//@todo this can be optimized when needed
var rule, strTgt,
i = 0,
rl = rules.length;
for (; i < rl; i++) {
rule = this.$bindings.getRuleIndex("drop", i);
for (var j = 0, l = x.length; j < l; j++) {
data = rule.cvalue ? rule.cvalue(x[j]) : rule.cmatch(x[j]);
if (!data)
break;
}
if (j != l)
continue;
strTgt = rule.target;//node.getAttribute("target");
if (!strTgt || strTgt == ".") {
//op = node.getAttribute("action")
//|| (this.$isTreeArch ? "tree-append" : "list-append");
tgt = target;/*(op == "list-append" || target == this.xmlRoot
? this.xmlRoot
: null);*/
}
else {
tgt = (rule.ctarget || rule.compile("target"))(target);
}
if (tgt && !apf.isChildOf(data, tgt, true))
return [tgt, rule];
}
return false;
};
this.$dragDrop = function(xmlReceiver, xmlNodeList, rule, defaction, isParent, srcRule, event, forceCopy) {
/*
Possibilities:
tree-append [default]: xmlNode.appendChild(movedNode);
list-append : xmlNode.parentNode.appendChild(movedNode);
insert-before : xmlNode.parentNode.insertBefore(movedNode, xmlNode);
*/
var action = rule && rule.action;//node && node.getAttribute("action");
if (action)
action = (rule.caction || rule.compile("action"))(xmlNodeList[0]);
else
action = defaction;
// @todo apf3.0 action not known here yet... should be moved down?
if (action == "tree-append" && isParent)
return false;
if (!event)
event = {};
//copy convenience variables
var context = {
internal: apf.DragServer.dragdata && apf.DragServer.dragdata.host == this,
ctrlKey: event.ctrlKey,
keyCode: event.keyCode
},
//@todo apf3.0 below should actually be compileNode with with_options
ifcopy = rule && rule.copy;//.getAttribute("copy");
if (typeof forceCopy == "boolean")
ifcopy = forceCopy;
else if (ifcopy) {
context.event = event || {};
ifcopy = !apf.isFalse((rule.ccopy || rule.compile("copy"))(xmlNodeList[0], context));
}
else if (typeof this.dragcopy == "boolean" || typeof this.dropcopy == "boolean") { //@todo apf3.0 boolean here?
if (this.dropcopy) {
ifcopy = this.dropcopy;
}
else if (this.dragcopy) {
ifcopy = event.ctrlKey;
}
else {
//@todo read this from src
var copyRule = this.$attrBindings && this.$attrBindings["dragcopy"];
if (copyRule) {
ifcopy = !apf.isFalse((copyRule.cvalue2
|| copyRule.compile("value", {
withopt: true
}))(xmlNodeList[0], context));
}
}
}
if (!ifcopy && srcRule) { //Implemented one copy is all copy
for (var i = 0, l = srcRule.length; i < l; i++) {
ifcopy = typeof srcRule[i] == "object" && srcRule[i].copy
? !apf.isFalse((srcRule[i].ccopy || srcRule[i].compile("copy"))(xmlNodeList[0], context))
: event.ctrlKey;
if (ifcopy) break;
}
}
var sNode,
actRule = ifcopy ? "copy" : "move",
parentXpath = rule ? rule.getAttribute("parent") : null; //@todo apf3.0 Should be lm syntax
switch (action) {
case "list-append":
xmlReceiver = (isParent
? xmlReceiver
: this.getTraverseParent(xmlReceiver));
if (parentXpath) {
if (xmlReceiver.selectSingleNode(parentXpath))
xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
else {
xmlReceiver.appendChild(xmlReceiver.ownerDocument.createElement(parentXpath));
xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
}
}
sNode = this[actRule](xmlNodeList, xmlReceiver);
break;
case "insert-before":
sNode = isParent
? this[actRule](xmlNodeList, xmlReceiver)
: this[actRule](xmlNodeList, xmlReceiver.parentNode, xmlReceiver);
break;
case "tree-append":
if (parentXpath) {
if (xmlReceiver.selectSingleNode(parentXpath))
xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
else {
xmlReceiver.appendChild(xmlReceiver.ownerDocument.createElement(parentXpath));
xmlReceiver = xmlReceiver.selectSingleNode(parentXpath);
}
}
sNode = this[actRule](xmlNodeList, xmlReceiver);
break;
}
if (this.selectable && sNode) {
this.selectList(sNode);//, null, null, null, true);
this.setCaret(sNode[0]);
this.focus();
}
return sNode;
};
/* **********************
Init
***********************/
/*
* Loads the dragdrop rules from the dragdrop element
*
* @param {Array} rules The rules array created using {@link core.apf.method.getrules}
* @param {XMLElement} [node] The reference to the drag & drop element
* @see SmartBinding
* @private
*/
this.enableDragDrop = function(){
//Set cursors
//SHOULD come from skin
this.icoAllowed = "";//this.xmlDragDrop.getAttribute("allowed");
this.icoDenied = "";//this.xmlDragDrop.getAttribute("denied");
//Setup External Object
this.$ext.dragdrop = false;
var _self = this;
this.$ext[apf.isIphone ? "ontouchstart" : "onmousedown"] = function(e) {
if (_self.disabled)
return;
e = e || window.event;
var fEl,
srcEl = e.originalTarget || e.srcElement || e.target,
multiselect = _self.hasFeature(apf.__MULTISELECT__);
if (multiselect && srcEl == _self.$container)
return;
_self.dragging = 0;
try{ //Firefox can crash here because of some chrome permission issue
if (!apf.isIphone && _self.allowdeselect
&& (srcEl == this || srcEl.getAttribute(apf.xmldb.htmlIdTag)
&& _self.$getLayoutNode("item", "select", this) != this))
return; //This broke making a selection with the mouse in rename: _self.clearSelection(); //@todo hacky - should detect what element has the select from the skin
}catch(e) {return;}
//MultiSelect must have carret behaviour AND deselect at clicking white
if (_self.$findValueNode)
fEl = _self.$findValueNode(srcEl);
var el = (fEl
? apf.xmldb.getNode(fEl)
: apf.xmldb.findXmlNode(srcEl));
if (multiselect && (!_self.selected || !el || el == _self.xmlRoot))
return;
if (_self.isDragAllowed(multiselect ? _self.$getSelection() : el)) {
apf.DragServer.start(_self, srcEl, e);
}
//e.cancelBubble = true;
};
this.$ext[apf.isIphone ? "ontouchmove" : "onmousemove"] = function(e) {
if (this.host.dragging != 1 || _self.disabled) return;
};
{
this.$ext.onmouseup = function(){
if (_self.disabled)
return;
this.host.dragging = 0;
};
this.$ext.ondragcopy =
this.$ext.ondragstart = function(){return false;};
}
if (document.elementFromPointAdd)
document.elementFromPointAdd(this.$ext);
if (this.$initDragDrop && !this.$dragInited) {
this.$initDragDrop();
this.$dragInited = 2;
}
else {
this.$dragInited = true;
}
};
function disableDragDrop(){
this.$dragInited = false; //@todo solve oExt event conflicts
{
this.$ext.onmousedown = this.$ext.onmousemove
= this.$ext.onmouseup = null;
}
if (document.elementFromPointRemove)
document.elementFromPointRemove(this.$ext);
}
this.implement(
this.hasFeature(apf.__MULTISELECT__)
? apf.MultiselectDragDrop :
apf.StandardDragDrop);
//this.$booleanProperties["drag"] = true;
//this.$booleanProperties["dragcopy"] = true;
this.$supportedProperties.push("drop", "drag", "dragcopy");
/**
* @attribute {Boolean} drag Sets or gets whether the element allows dragging of its items.
*
* #### Example
*
* ```xml
*
*
* item 1
* item 2
* item 3
*
*```
*
*/
/**
* @attribute {Boolean} dragcopy whether dragged items are copied.
*
* #### Example
*
* ```xml
*
*
*
*
*
*
*
*
*
* ```
*
* #### Example
*
* Items are only copied when the user holds the [[keys: Ctrl]] key
*
* ```xml
*
* item 1
* item 2
* item 3
*
* ```
*/
/**
* @attribute {Boolean} drop Sets or gets whether the element allows items to be dropped.
*
* #### Example
*
*
* ```xml
*
* item 1
* item 2
* item 3
*
* ```
* @attribute {String} dragdrop Sets or gets the name of the dragdrop element for this element.
*
* ```xml
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
*
* ```
*/
this.$propHandlers["dragcopy"] =
this.$propHandlers["dropcopy"] =
this.$propHandlers["drag"] =
this.$propHandlers["drop"] = function(value, prop) {
this[prop] = apf.isTrue(value);
if (this.$dragInited && prop == "drag" && value && this.$dragInited != 2) {
this.$initDragDrop();
this.$dragInited = 2;
return;
}
if (prop == "dragcopy" || prop == "dropcopy")
return;
if (!value && !this.drag && !this.drop && !this.$bindings
&& (this.$attrBindings && (!this.$attrBindings["drag"] || !this.$attrBindings["drop"])))
disableDragDrop.call(this);
else if (value && !this.$dragInited)
this.enableDragDrop();
};
this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
disableDragDrop.call(this);
if (this.oDrag) {
apf.destroyHtmlNode(this.oDrag);
this.oDrag = null;
}
});
};
apf.GuiElement.propHandlers["dragcopy"] =
apf.GuiElement.propHandlers["dropcopy"] =
apf.GuiElement.propHandlers["drop"] =
apf.GuiElement.propHandlers["drag"] = function(value, prop) {
if (!apf.isFalse(value)) {
if (!this.hasFeature(apf.__DRAGDROP__)) {
this.implement(apf.DragDrop);
this.enableDragDrop();
}
this[prop] = apf.isTrue(value);
}
};
/*
* Central object for dragdrop handling.
* @private
*/
apf.DragServer = {
Init: function(){
apf.addEventListener("hotkey", function(e) {
if (apf.window.dragging && e.keyCode == 27) {
if (document.body.lastHost && document.body.lastHost.dragOut)
document.body.lastHost.dragOut(apf.dragHost);
return apf.DragServer.stopdrag();
}
});
},
start: function(amlNode, srcEl, e, customNode) {
if (document.elementFromPointReset)
document.elementFromPointReset();
amlNode.dragging = 1;
var d = window.document;
d = (!d.compatMode || d.compatMode == "CSS1Compat")
? d.html || d.documentElement
: d.body
var scrollX = (apf.isIE ? d.scrollLeft : window.pageXOffset),
scrollY = (apf.isIE ? d.scrollTop : window.pageYOffset),
oParent = amlNode.$ext.offsetParent,
pos
while (oParent && oParent != d && oParent.tagName != "BODY") {
scrollX -= oParent.scrollLeft;
scrollY -= oParent.scrollTop;
oParent = oParent.offsetParent;
}
//The coordinates need to be relative to the html element that
//represents the xml data node.
if (!srcEl && customNode) {
pos = [0, 0];
}
else {
var loopEl = srcEl, lastId;
while (loopEl && loopEl.nodeType == 1
&& !(lastId = loopEl.getAttribute(apf.xmldb.htmlIdTag))) {
loopEl = loopEl.parentNode;
}
if (!lastId)
return;
pos = apf.getAbsolutePosition(loopEl);
}
//Set coordinates object
apf.DragServer.coordinates = {
srcElement: srcEl,
doc: d,
scrollX: scrollX,
scrollY: scrollY,
offsetX: e.clientX - pos[0],
offsetY: e.clientY - pos[1],
clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX,
clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY
};
//Create Drag Data Object
var selection = customNode || amlNode.hasFeature(apf.__MULTISELECT__)
? amlNode.getSelection()
: [amlNode.xmlRoot],
data = [],
srcRules = amlNode.isDragAllowed(selection, data);
if (!srcRules) return;
if (amlNode.hasEventListener("dragdata"))
data = amlNode.dispatchEvent("dragdata", {data : data});
/*for(var i = 0, l = data.length; i < l; i++) {
data[i] = apf.getCleanCopy(data[i]);
}*/
this.dragdata = {
rules: srcRules,
selection: selection,
data: data,
indicator: amlNode.$showDragIndicator(selection, this.coordinates),
host: amlNode
};
//EVENT - cancelable: ondragstart
if (amlNode.dispatchEvent("dragstart", this.dragdata) === false)
return false;//(this.amlNode.$tempsel ? select(this.amlNode.$tempsel) : false);
amlNode.dragging = 2;
apf.dragMode = true;
document.onmousemove = this.onmousemove;
document.onmouseup = this.onmouseup;
},
stop: function(runEvent, success, e) {
if (this.last) this.dragout();
this.dragdata.host.dispatchEvent("dragstop", apf.extend(this.dragdata, {
success: success
}));
//Reset Objects
this.dragdata.host.dragging = 0;
this.dragdata.host.$hideDragIndicator(success);
/*if (runEvent && this.dragdata.host.$dragstop)
this.dragdata.host.$dragstop();*/
apf.dragMode = false;
document.onmousemove =
document.onmouseup = null;
this.dragdata = null;
},
dragover: function(o, el, e) {
var _self = this,
originalEl = el;
function checkPermission(targetEl) {
return o.isDropAllowed && o.xmlRoot
? o.isDropAllowed(_self.dragdata.data, targetEl)
: apf.isTrue(apf.getInheritedAttribute(o, "", function(p) {
if (p.drop) {
o = p;
if (o == apf.DragServer.last)
return false;
return true;
}
}));
}
e = e || window.event;
//@todo optimize by not checking the same node dragged over twice in a row
var fEl;
if (o.$findValueNode)
fEl = o.$findValueNode(el);
if (this.lastFel && this.lastFel == fEl
|| !this.lastFel && this.last == o) //optimization
return;
//Check Permission
var elSel = (fEl
? apf.xmldb.getNode(fEl)
: apf.xmldb.findXmlNode(el)),
candrop = checkPermission(elSel || o.xmlRoot);
if (this.last && this.last != o)
this.dragout(this.last, e);
this.last = o;
this.lastFel = fEl;
if (!candrop) {
if (o && o.$dragover) {
var parentNode = (elSel || o.xmlRoot).parentNode;
if (parentNode && (el = apf.xmldb.findHtmlNode(parentNode, o))) {
if (o.$findValueNode)
fEl = o.$findValueNode(el);
elSel = (fEl
? apf.xmldb.getNode(fEl)
: apf.xmldb.findXmlNode(el));
candrop = checkPermission(parentNode);
this.lastFel = el;
if (!candrop)
return;
}
else
return;
}
else
return;
}
//EVENT - cancelable: ondragover
if (o.dispatchEvent("dragover", this.dragdata, {
target: (elSel || o.xmlRoot),
lastEl: o.lastel,
originalEl: originalEl
}) === false)
candrop = false;
//Set Cursor
var srcEl = e.originalTarget || e.srcElement || e.target;
/*srcEl.style.cursor = (candrop ? o.icoAllowed : o.icoDenied);
if (srcEl.onmouseout != this.m_out) {
srcEl.$onmouseout = srcEl.onmouseout;
srcEl.onmouseout = this.m_out;
}
o.$ext.style.cursor = (candrop ? o.icoAllowed : o.icoDenied);*/
//REQUIRED INTERFACE: __dragover()
if (o && o.$dragover)
o.$dragover(el, this.dragdata, candrop);
},
dragout: function(o, e) {
//if (this.last == o)
//return false;
this.lastFel = null;
//EVENT: ondragout
if (o) {
this.dragdata.htmlEvent = e;
o.dispatchEvent("dragout", this.dragdata);
}
//REQUIRED INTERFACE: __dragout()
if (this.last && this.last.$dragout)
this.last.$dragout(null, this.dragdata);
//Reset Cursor
//o.$ext.style.cursor = "default";
this.last = null;
},
dragdrop: function(o, el, srcO, e) {
var _self = this;
function checkPermission(targetEl) {
return o.isDropAllowed && o.xmlRoot
? o.isDropAllowed(_self.dragdata.data, targetEl)
: apf.isTrue(apf.getInheritedAttribute(o, "", function(p) {
if (p.drop) {
o = p;
return true;
}
}));
}
//Check Permission
var isParent, lastTop,
elSel = (o.$findValueNode
? apf.xmldb.getNode(o.$findValueNode(el))
: apf.xmldb.findXmlNode(el)),
candrop = checkPermission(elSel || o.xmlRoot);
if (this.dragdata.indicator) {
lastTop = this.dragdata.indicator.style.top;
this.dragdata.indicator.style.top = "10000px";
}
if (!candrop) {
if (o && o.$dragover) {
var parentNode = (elSel || o.xmlRoot).parentNode,
htmlParentNode;
if (parentNode && (htmlParentNode = apf.xmldb.findHtmlNode(parentNode, o))) {
isParent = true;
candrop = checkPermission(parentNode);
el = htmlParentNode;
}
}
}
//EVENT - cancelable: ondragdrop
if (candrop) {
if (o.dispatchEvent("dragdrop", apf.extend({candrop : candrop, htmlEvent : e, top: lastTop},
this.dragdata)) === false) {
candrop = false;
}
else {
if (!o.xmlRoot) {
var m = o.getModel
? o.getModel(true)
:
apf.nameserver.get("model", o.model)
if (m)
m.load(this.dragdata.data[0])
//else warn??
return true;
}
else {
var action = candrop[1]
&& candrop[1].action
|| (o.$isTreeArch ? "tree-append" : "list-append");
if (action == "list-append" && (!o.$isTreeArch && o == this.dragdata.host))
candrop = false;
}
}
}
if (this.dragdata.indicator)
this.dragdata.indicator.style.top = lastTop;
//Exit if not allowed
if (!candrop) {
this.dragout(o, e);
return false;
}
if (o.$dragDrop) {
//Move XML
var rNode = o.$dragDrop(candrop[0], this.dragdata.data, candrop[1],
action, isParent || candrop[0] == o.xmlRoot, this.dragdata.rules, e);
this.dragdata.resultNode = rNode;
}
if (o.$dragdrop) {
o.$dragdrop(el, apf.extend({
htmlEvent: e,
xmlNode: rNode
}, this.dragdata), candrop);
}
//Reset Cursor
//o.$ext.style.cursor = "default";
this.last = null;
this.lastFel = null;
return true;
},
/* **********************
Mouse Movements
***********************/
onmousemove: function(e) {
if (!apf.DragServer.dragdata) return;
e = e || window.event;
var dragdata = apf.DragServer.dragdata,
c = {
clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX,
clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY
};
if (!dragdata.started
&& Math.abs(apf.DragServer.coordinates.clientX - c.clientX) < 6
&& Math.abs(apf.DragServer.coordinates.clientY - c.clientY) < 6)
return;
if (!dragdata.started) {
if (dragdata.host.$dragstart)
dragdata.host.$dragstart(null, dragdata);
dragdata.started = true;
}
//dragdata.indicator.style.top = e.clientY+"px";
//dragdata.indicator.style.left = e.clientX+"px";
if (dragdata.indicator) {
var storeIndicatorTopPos = dragdata.indicator.style.top;
//console.log("INDICATOR BEFORE: "+dragdata.indicator.style.top+" "+dragdata.indicator.style.left);
//get Element at x, y
dragdata.indicator.style.display = "block";
dragdata.indicator.style.top = "10000px";
}
apf.DragServer.dragdata.x = e.pageX ? e.pageX - (!apf.isIE
? window.pageXOffset
: 0) : c.clientX;
apf.DragServer.dragdata.y = e.pageY ? e.pageY - (!apf.isIE
? window.pageYOffset
: 0) : c.clientY;
var el = document.elementFromPoint(apf.DragServer.dragdata.x,
apf.DragServer.dragdata.y);
if (!el) {
el = document.elementFromPoint(apf.DragServer.dragdata.x,
apf.DragServer.dragdata.y);
}
if (dragdata.indicator)
dragdata.indicator.style.top = storeIndicatorTopPos;
//console.log("INDICATOR AFTER: "+dragdata.indicator.style.top+" "
//+dragdata.indicator.style.left+" "+apf.DragServer.dragdata.x+" "+apf.DragServer.dragdata.y);
//Set Indicator
dragdata.host.$moveDragIndicator(c);
//get element and call events
var receiver = apf.findHost(el);
//Run Events
if (receiver)
apf.DragServer.dragover(receiver, el, e);
else if (apf.DragServer.last)
apf.DragServer.dragout(apf.DragServer.last, e);
apf.DragServer.lastTime = new Date().getTime();
},
onmouseup: function(e) {
e = e || window.event;
var c = {
clientX: e.pageX ? e.pageX - window.pageXOffset : e.clientX,
clientY: e.pageY ? e.pageY - window.pageYOffset : e.clientY
};
if (!apf.DragServer.dragdata.started
&& Math.abs(apf.DragServer.coordinates.clientX - c.clientX) < 6
&& Math.abs(apf.DragServer.coordinates.clientY - c.clientY) < 6) {
apf.DragServer.stop(true, null, e)
return;
}
//get Element at x, y
var indicator = apf.DragServer.dragdata.indicator,
storeIndicatorTopPos = indicator.style.top;
//apf.console.info("INDICATOR UP BEFORE: "+indicator.style.top+" "+indicator.style.left);
if (indicator)
indicator.style.top = "10000px";
apf.DragServer.dragdata.x = e.pageX ? e.pageX - (!apf.isIE
? window.pageXOffset
: 0) : c.clientX;
apf.DragServer.dragdata.y = e.pageY ? e.pageY - (!apf.isIE
? window.pageYOffset
: 0) : c.clientY;
var el = document.elementFromPoint(apf.DragServer.dragdata.x,
apf.DragServer.dragdata.y);
if (!el) {
el = document.elementFromPoint(apf.DragServer.dragdata.x,
apf.DragServer.dragdata.y);
}
indicator.style.top = storeIndicatorTopPos;
//apf.console.info("INDICATOR UP AFTER: "+indicator.style.top+" "+indicator.style.left);
//get element and call events
var host = apf.findHost(el);
//Run Events
if (apf.DragServer.host && host != apf.DragServer.host)
apf.DragServer.dragout(apf.DragServer.host, e);
var success = apf.DragServer.dragdrop(host, el, apf.DragServer.dragdata.host, e);
apf.DragServer.stop(true, success, e);
}
};
/*
* @private
*/
apf.MultiselectDragDrop = function() {
// *** Drag & Drop *** //
this.diffX =
this.diffY = 0;
this.multiple = false;
this.lastDragNode = null;
this.lastel = null;
this.$showDragIndicator = function(sel, e) {
var srcEl = e.originalTarget || e.srcElement || e.target;
this.multiple = sel.length > 1;
if (this.multiple) {
this.diffX = e.scrollX;
this.diffY = e.scrollY;
}
else {
var itemNode = apf.xmldb.findHtmlNode(sel[0], this);
this.diffX = -1 * (e.offsetX - parseInt(apf.getStyleRecur(itemNode, "padding-left").replace(/px$/, "") - 10));
this.diffY = -1 * e.offsetY;
}
var prefix = this.oDrag.className.split(" ")[0]
//@todo the class should be removed here
this.$setStyleClass(this.oDrag, (this.multiple
? prefix + "_multiple" : "") + (this["class"] ? " " + this["class"] : ""), [prefix + "_multiple"]);
if (this.multiple) {
document.body.appendChild(this.oDrag);
return this.oDrag;
}
else if (this.localName == "datagrid") {
if (this.lastDragNode)
apf.destroyHtmlNode(this.lastDragNode);
sel = this.$selected || this.$caret;
var oDrag = sel.cloneNode(true);
oDrag.removeAttribute("onmousedown");oDrag.onmousedown = null;
oDrag.removeAttribute("onmouseup");oDrag.onmouseup = null;
oDrag.removeAttribute("onmouseout");oDrag.onmouseout = null;
oDrag.removeAttribute("ondblclick");oDrag.ondblclick = null;
document.body.appendChild(oDrag);
oDrag.style.position = "absolute";
oDrag.style.width = sel.offsetWidth + "px";
oDrag.style.display = "none";
oDrag.removeAttribute("id");
this.$setStyleClass(oDrag, "draggrid");
var nodes = sel.childNodes;
var dragnodes = oDrag.childNodes;
for (var i = nodes.length - 1; i >= 0; i--) {
if (dragnodes[i].nodeType == 1)
dragnodes[i].style.width = apf.getStyle(nodes[i], "width");
}
//@todo apf3.0 remove all the event handlers of the children.
return (this.lastDragNode = oDrag);
}
else {
var sel = this.$selected || this.$caret,
width = apf.getStyle(this.oDrag, "width");
if (!sel)
return;
// if (!width || width == "auto")
// this.oDrag.style.width = (sel.offsetWidth - apf.getWidthDiff(this.oDrag)) + "px";
this.$updateNode(this.selected, this.oDrag);
}
apf.window.zManager.set("drag", this.oDrag);
return this.oDrag;
};
this.$hideDragIndicator = function(success) {
var oDrag = this.lastDragNode || this.oDrag, _self = this;
if (!this.multiple && !success && oDrag.style.display == "block") {
if (!this.$selected && !this.$caret)
return;
var pos = apf.getAbsolutePosition(this.$selected || this.$caret);
apf.tween.multi(oDrag, {
anim: apf.tween.easeInOutCubic,
steps: 20,
interval: 15,
tweens: [
{type: "left", from: oDrag.offsetLeft, to: (pos[0] + parseInt(apf.getStyleRecur(this.$selected, "padding-left").replace(/px$/, "")))},
{type: "top", from: oDrag.offsetTop, to: pos[1]}
],
onfinish: function(){
if (_self.lastDragNode) {
apf.destroyHtmlNode(_self.lastDragNode);
_self.lastDragNode = null;
}
else {
_self.oDrag.style.display = "none";
}
}
});
}
else if (this.lastDragNode) {
apf.destroyHtmlNode(this.lastDragNode);
this.lastDragNode = null;
}
else {
this.oDrag.style.display = "none";
}
};
this.$moveDragIndicator = function(e) {
var oDrag = this.lastDragNode || this.oDrag;
oDrag.style.left = (e.clientX + this.diffX) + "px";// - this.oDrag.startX
oDrag.style.top = (e.clientY + this.diffY + (this.multiple ? 15 : 0)) + "px";// - this.oDrag.startY
};
this.addEventListener("$skinchange", function(){
this.$initDragDrop();
});
this.$initDragDrop = function(){
if (!this.$hasLayoutNode("dragindicator"))
return;
this.oDrag = apf.insertHtmlNode(
this.$getLayoutNode("dragindicator"), document.body);
apf.window.zManager.set("drag", this.oDrag);
this.oDrag.style.position = "absolute";
this.oDrag.style.cursor = "default";
this.oDrag.style.display = "none";
};
this.$findValueNode = function(el) {
if (!el) return null;
while (el && el.nodeType == 1
&& !el.getAttribute(apf.xmldb.htmlIdTag)) {
if (this.$isTreeArch && el.previousSibling
&& el.previousSibling.nodeType == 1) //@todo hack!! apf3.0 fix this.
el = el.previousSibling;
else
el = el.parentNode;
}
return (el && el.nodeType == 1 && el.getAttribute(apf.xmldb.htmlIdTag))
? el
: null;
};
this.$dragout = function(el, dragdata, extra) {
if (this.lastel)
this.$setStyleClass(this.lastel, "", ["dragDenied", "dragInsert",
"dragAppend", "selected", "indicate"]);
var sel = this.$getSelection(true);
for (var i = 0, l = sel.length; i < l; i++)
this.$setStyleClass(sel[i], "selected", ["dragDenied",
"dragInsert", "dragAppend", "indicate"]);
this.$setStyleClass(this.$ext, "", [this.$baseCSSname + "Drop"]);
this.lastel = null;
};
if (!this.$dragdrop)
this.$dragdrop = this.$dragout;
this.$dragover = function(el, dragdata, extra) {
this.$setStyleClass(this.$ext, this.$baseCSSname + "Drop");
var sel = this.$getSelection(true);
for (var i = 0, l = sel.length; i < l; i++)
this.$setStyleClass(sel[i], "", ["dragDenied",
"dragInsert", "dragAppend", "selected", "indicate"]);
if (this.lastel)
this.$setStyleClass(this.lastel, "", ["dragDenied",
"dragInsert", "dragAppend", "selected", "indicate"]);
var action = extra[1] && extra[1].action;
this.lastel = this.$findValueNode(el);
if (this.$isTreeArch && action == "list-append") {
var htmlNode = apf.xmldb.findHtmlNode(this.getTraverseParent(apf.xmldb.getNode(this.lastel)), this);
this.lastel = htmlNode
? this.$getLayoutNode("item", "container", htmlNode)
: this.$container;
this.$setStyleClass(this.lastel, "dragInsert");
}
else {
this.$setStyleClass(this.lastel, extra
? (action == "insert-before"
? "dragInsert"
: "dragAppend")
: "dragDenied");
}
};
};
/*
* @private
*/
apf.StandardDragDrop = function() {
this.$showDragIndicator = function(sel, e) {
var x = e.offsetX + 22,
y = e.offsetY;
this.oDrag.startX = x;
this.oDrag.startY = y;
document.body.appendChild(this.oDrag);
//this.oDrag.getElementsByTagName("DIV")[0].innerHTML = this.selected.innerHTML;
//this.oDrag.getElementsByTagName("IMG")[0].src = this.selected.parentNode.parentNode.childNodes[1].firstChild.src;
var oInt = this.$getLayoutNode("main", "caption", this.oDrag);
if (oInt.nodeType != 1)
oInt = oInt.parentNode;
oInt.innerHTML = this.$applyBindRule("caption", this.xmlRoot) || "";
return this.oDrag;
};
this.$hideDragIndicator = function(){
this.oDrag.style.display = "none";
};
this.$moveDragIndicator = function(e) {
this.oDrag.style.left = (e.clientX - this.oDrag.startX
+ document.documentElement.scrollLeft) + "px";
this.oDrag.style.top = (e.clientY - this.oDrag.startY
+ document.documentElement.scrollTop) + "px";
};
//@todo falsely assuming only attributes are used for non multiselect widgets
this.$initDragDrop = function(){
if (!this.getAttribute("drag"))
return;
this.oDrag = document.body.appendChild(this.$ext.cloneNode(true));
apf.window.zManager.set("drag", this.oDrag);
this.oDrag.style.position = "absolute";
this.oDrag.style.cursor = "default";
this.oDrag.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=50)";
this.oDrag.style.MozOpacity = 0.5;
this.oDrag.style.opacity = 0.5;
this.oDrag.style.display = "none";
};
};
apf.DragServer.Init();
apf.__FOCUSSABLE__ = 1 << 26;
/**
* All elements inheriting from this {@link term.baseclass baseclass} have focussable
* features
*
* @class apf.Focussable
* @baseclass
*
*/
apf.Focussable = function(){
this.$regbase = this.$regbase | apf.__FOCUSSABLE__;
if (this.disabled == undefined)
this.disabled = false;
/**
* Sets the position in the list that determines the sequence
* of elements when using the tab key to move between them.
* @chainable
* @param {Number} tabindex The position in the list
*/
this.setTabIndex = function(tabindex) {
apf.window.$removeFocus(this);
apf.window.$addFocus(this, tabindex);
return this;
};
/**
* Gives this element the focus. This means that keyboard events
* are sent to this element.
* @chainable
*/
this.focus = function(noset, e, nofix) {
if (!noset) {
if (this.$isWindowContainer > -1) {
apf.window.$focusLast(this, e, true);
}
else {
apf.window.$focus(this, e);
}
return this;
}
if (this.$focus && !this.editable && (!e || !e.mouse || this.$focussable == apf.KEYBOARD_MOUSE))
this.$focus(e);
this.dispatchEvent("focus", apf.extend({
bubbles: true
}, e));
return this;
};
/**
* Removes the focus from this element.
* @chainable
*/
this.blur = function(noset, e) {
if ((e && !apf.isChildOf(e.fromElement, e.toElement)) && apf.popup.isShowing(this.$uniqueId) && e.toElement.localName != "menu")
apf.popup.forceHide(); //This should be put in a more general position
if (this.$blur)
this.$blur(e);
if (!noset)
apf.window.$blur(this);
this.dispatchEvent("blur", apf.extend({
bubbles: !e || !e.cancelBubble
}, e));
return this;
};
/**
* Determines whether this element has the focus
* @returns {Boolean} Indicates whether this element has the focus
*/
this.hasFocus = function(){
return apf.document.activeElement == this || this.$isWindowContainer
&& (apf.document.activeElement || {}).$focusParent == this;
};
};
apf.__INTERACTIVE__ = 1 << 21;
/**
* All elements inheriting from this {@link term.baseclass baseclass} have interactive features, making an
* element draggable and resizable.
*
* #### Example
*
* ```xml
*
* ```
*
* @class apf.Interactive
* @baseclass
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 1.0
*
* @see apf.appsettings.outline
* @see apf.appsettings.resize-outline
* @see apf.appsettings.drag-outline
*/
/**
* @attribute {Boolean} draggable Sets or gets whether an element is draggable. The user will
* able to move the element around while holding the mouse button down on the
* element.
*
* #### Example
*
* ```xml
*
* ```
*/
/**
* @attribute {Boolean} resizable Sets or gets whether an element is resizable.
*
* The user will able
* to resize the element by grabbing one of the four edges of the element and
* pulling it in either direction. Grabbing the corners allows users to
* resize horizontally and vertically at the same time. The right bottom corner
* is special, because it offers an especially big grab area. The size of this
* area can be configured in the skin of the element.
*
* #### Example
*
* ```xml
*
* ```
*/
/**
* @attribute {Number} minwidth Sets or gets the minimum horizontal size the element can get when resizing.
*/
/**
* @attribute {Number} minheight Sets or gets the minimum vertical size the element can get when resizing.
*/
/**
* @attribute {Number} maxwidth Sets or gets the maximum horizontal size the element can get when resizing.
*/
/**
* @attribute {Number} maxheight Sets or gets the maximum vertical size the element can get when resizing.
*
*/
/**
* @event drag Fires when the widget has been dragged.
*/
/**
* @event resizestart Fires before the widget is resized.
* @cancelable Prevents this resize action to start.
* @param {Object} e The standard event object. It contains the following property:
* - type ([[String]]): the type of resize. This is a combination of the four directions--`"n"`, `"s"`, `"e"`, `"w"`.
*/
/**
* @event resize Fires when the widget has been resized.
*
*/
apf.Interactive = function(){
var nX, nY, rX, rY, startPos, lastCursor = null, l, t, r, b, lMax, tMax, lMin,
tMin, w, h, we, no, ea, so, rszborder, rszcorner, marginBox,
verdiff, hordiff, _self = this, posAbs, oX, oY, overThreshold,
dragOutline, resizeOutline, myPos, startGeo;
this.$regbase = this.$regbase | apf.__INTERACTIVE__;
this.$dragStart = function(e, reparent) {
var nativeEvent = e || event;
if (!reparent && nativeEvent.button == 2)
return;
{
dragStart.apply(nativeEvent.srcElement || this, arguments);
}
}
this.$propHandlers["draggable"] = function(value) {
if (apf.isFalse(value))
this.draggable = value = false;
else if (apf.isTrue(value))
this.draggable = value = true;
var o = this.editable ? this.$ext : this.oDrag || this.$ext;
if (value)
apf.addListener(o, "mousedown", this.$dragStart);
else
apf.removeListener(o, "mousedown", this.$dragStart);
//deprecated??
if (o.interactive & 1)
return;
o.interactive = (o.interactive||0)+1;
//this.$ext.style.position = "absolute";
};
this.$propHandlers["resizable"] = function(value) {
if (apf.isFalse(value))
this.resizable = false;
else if (apf.isTrue(value))
this.resizable = "true";
this.$ext.style.cursor = "";
var o = this.oResize || this.$ext;
if (o.interactive & 2)
return;
if (!_self.editable) {
apf.addListener(o, "mousedown", function(){
resizeStart.apply(o, arguments);
});
apf.addListener(o, "mousemove", function(){
resizeIndicate.apply(o, arguments);
});
}
o.interactive = (o.interactive||0)+2;
//this.$ext.style.position = "absolute";
rszborder = this.$getOption && parseInt(this.$getOption("Main", "resize-border")) || 3;
rszcorner = this.$getOption && parseInt(this.$getOption("Main", "resize-corner")) || 12;
marginBox = apf.getBox(apf.getStyle(this.$ext, "borderWidth"));
};
/*
this.$propHandlers["minwidth"] =
this.$propHandlers["maxwidth"] =
this.$propHandlers["minheight"] =
this.$propHandlers["maxheight"] = function(value, prop) {
if (this.aData)
this.aData[prop] = parseInt(value);
}
if (this.aData) {
this.aData.minwidth = this.minwidth;
this.aData.minheight = this.minheight;
}*/
this.$cancelInteractive = function(){
document.onmouseup(null, true);
}
function dragStart(e, reparent) {
if (!e) e = event;
if (!reparent && (!_self.draggable || apf.dragMode))//_self.editable ||
return;
dragOutline = false;
var host = apf.findHost(e.target)
if (host && host.textselect)
return;
if (_self.dispatchEvent("beforedragstart", {htmlEvent: e}) === false)
return;
apf.dragMode = true;
if (reparent) {
_self.dispatchEvent("beforedrag")
overThreshold = true;
}
else
overThreshold = false;
apf.popup.forceHide();
posAbs = "absolute|fixed".indexOf(apf.getStyle(_self.$ext, "position")) > -1;
if (!posAbs) {
_self.$ext.style.position = posAbs //(posAbs = _self.dragSelection)
? "absolute" : "relative";
}
if (_self.editable)
posAbs = true;
//@todo not for docking
if (posAbs && !_self.aData) {
apf.plane.show(dragOutline
? oOutline
: _self.$ext, e.reappend);//, true
}
var ext = (reparent || (oOutline && oOutline.self)) && dragOutline //little dirty hack to detect outline set by visualselect
? oOutline
: _self.$ext;
var pos = posAbs
? apf.getAbsolutePosition(ext, ext.offsetParent, true)
: [parseInt(apf.getStyle(ext, "left")) || 0,
parseInt(apf.getStyle(ext, "top")) || 0];
startGeo = [ext.style.left, ext.style.top, ext.style.right,
ext.style.bottom, ext.style.width, ext.style.height];
nX = pos[0] - (oX = e.clientX);
nY = pos[1] - (oY = e.clientY);
//if (_self.hasFeature && _self.hasFeature(apf.__ANCHORING__))
//_self.$disableAnchoring();
if (!(reparent || (oOutline && oOutline.self))) {
{
if (_self.$ext.style.right) {
_self.$ext.style.left = pos[0] + "px";
_self.$ext.style.right = "";
}
if (_self.$ext.style.bottom) {
_self.$ext.style.top = pos[1] + "px";
_self.$ext.style.bottom = "";
}
}
}
document.onmousemove = dragMove;
document.onmouseup = function(e, cancel) {
document.onmousemove = document.onmouseup = null;
if (posAbs && !_self.aData)
apf.plane.hide();
var htmlNode = dragOutline
? oOutline
: _self.$ext;
if (overThreshold && !_self.$multidrag) {
if (cancel) {
var ext = _self.$ext;
ext.style.left = startGeo[0];
ext.style.top = startGeo[1];
ext.style.right = startGeo[2];
ext.style.bottom = startGeo[3];
ext.style.width = startGeo[4];
ext.style.height = startGeo[5];
if (_self.dispatchEvent)
_self.dispatchEvent("dragcancel", {
htmlNode: htmlNode,
htmlEvent: e
});
}
else
if (_self.setProperty) {
updateProperties();
}
else if (dragOutline) {
_self.$ext.style.left = l + "px";
_self.$ext.style.top = t + "px";
}
}
l = t = w = h = null;
if (!posAbs)
_self.$ext.style.position = "relative";
if (_self.showdragging)
apf.setStyleClass(_self.$ext, "", ["dragging"]);
if (posAbs && dragOutline && !oOutline.self) //little dirty hack to detect outline set by visualselect
oOutline.style.display = "none";
apf.dragMode = false;
if (!cancel && _self.dispatchEvent && overThreshold)
_self.dispatchEvent("afterdrag", {
htmlNode: htmlNode,
htmlEvent: e
});
};
if (reparent)
document.onmousemove(e);
return false;
};
function dragMove(e) {
if (!e) e = event;
//if (_self.dragSelection)
//overThreshold = true;
if (!overThreshold && _self.showdragging)
apf.setStyleClass(_self.$ext, "dragging");
// usability rule: start dragging ONLY when mouse pointer has moved delta x pixels
var dx = e.clientX - oX,
dy = e.clientY - oY,
distance;
if (!overThreshold
&& (distance = dx*dx > dy*dy ? dx : dy) * distance < 2)
return;
//Drag outline support
else if (!overThreshold) {
if (dragOutline
&& oOutline.style.display != "block")
oOutline.style.display = "block";
if (_self.dispatchEvent && _self.dispatchEvent("beforedrag", {htmlEvent: e}) === false) {
document.onmouseup();
return;
}
}
var oHtml = dragOutline
? oOutline
: _self.$ext;
oHtml.style.left = (l = e.clientX + nX) + "px";
oHtml.style.top = (t = e.clientY + nY) + "px";
if (_self.realtime) {
var change = _self.$stick = {};
_self.$showDrag(l, t, oHtml, e, change);
if (typeof change.l != "undefined")
l = change.l, oHtml.style.left = l + "px";
if (typeof change.t != "undefined")
t = change.t, oHtml.style.top = t + "px";
}
overThreshold = true;
};
this.$resizeStart = resizeStart;
function resizeStart(e, options) {
if (!e) e = event;
//|| _self.editable
if (!_self.resizable
|| String(_self.height).indexOf("%") > -1 && _self.parentNode.localName == "vbox" //can't resize percentage based for now
|| String(_self.width).indexOf("%") > -1 && _self.parentNode.localName == "hbox") //can't resize percentage based for now
return;
resizeOutline = false;
var ext = _self.$ext;
if (!resizeOutline) {
var diff = apf.getDiff(ext);
hordiff = diff[0];
verdiff = diff[1];
}
//@todo This is probably not gen purpose
startPos = apf.getAbsolutePosition(ext);//, ext.offsetParent);
startPos.push(ext.offsetWidth);
startPos.push(ext.offsetHeight);
myPos = apf.getAbsolutePosition(ext, ext.offsetParent, true);
startGeo = [ext.style.left, ext.style.top, ext.style.right,
ext.style.bottom, ext.style.width, ext.style.height];
var sLeft = 0,
sTop = 0,
x = (oX = e.clientX) - startPos[0] + sLeft + document.documentElement.scrollLeft,
y = (oY = e.clientY) - startPos[1] + sTop + document.documentElement.scrollTop,
resizeType;
if (options && options.resizeType) {
posAbs = "absolute|fixed".indexOf(apf.getStyle(ext, "position")) > -1;
resizeType = options.resizeType;
}
else {
resizeType = getResizeType.call(ext, x, y);
}
rX = x;
rY = y;
if (!resizeType)
return;
if (_self.dispatchEvent && _self.dispatchEvent("beforeresize", {
type: resizeType,
setType: function(type) {
resizeType = type;
}
}) === false) {
return;
}
apf.popup.forceHide();
//if (_self.hasFeature && _self.hasFeature(apf.__ANCHORING__))
//_self.$disableAnchoring();
apf.dragMode = true;
overThreshold = false;
we = resizeType.indexOf("w") > -1;
no = resizeType.indexOf("n") > -1;
ea = resizeType.indexOf("e") > -1;
so = resizeType.indexOf("s") > -1;
if (!_self.minwidth) _self.minwidth = 0;
if (!_self.minheight) _self.minheight = 0;
if (!_self.maxwidth) _self.maxwidth = 10000;
if (!_self.maxheight) _self.maxheight = 10000;
if (posAbs) {
lMax = myPos[0] + startPos[2];
tMax = myPos[1] + startPos[3];
lMin = myPos[0] + startPos[2];
tMin = myPos[1] + startPos[3];
}
if (posAbs) {
apf.plane.show(resizeOutline
? oOutline
: ext);//, true
}
var iMarginLeft;
{
if (ext.style.right) {
ext.style.left = myPos[0] + "px";
//console.log(myPos[0]);
//ext.style.right = "";
}
if (ext.style.bottom) {
ext.style.top = myPos[1] + "px";
//ext.style.bottom = "";
}
}
if (!options || !options.nocursor) {
if (lastCursor === null)
lastCursor = document.body.style.cursor;//apf.getStyle(document.body, "cursor");
document.body.style.cursor = getCssCursor(resizeType) + "-resize";
}
document.onmousemove = resizeMove;
document.onmouseup = function(e, cancel) {
document.onmousemove = document.onmouseup = null;
if (posAbs)
apf.plane.hide();
clearTimeout(timer);
if (resizeOutline) {
var diff = apf.getDiff(_self.$ext);
hordiff = diff[0];
verdiff = diff[1];
}
if (cancel) {
var ext = _self.$ext;
ext.style.left = startGeo[0];
ext.style.top = startGeo[1];
ext.style.right = startGeo[2];
ext.style.bottom = startGeo[3];
ext.style.width = startGeo[4];
ext.style.height = startGeo[5];
if (_self.dispatchEvent)
_self.dispatchEvent("resizecancel");
}
else
doResize(e || event, true);
if (_self.setProperty)
updateProperties();
document.body.style.cursor = lastCursor || "";
lastCursor = null;
if (resizeOutline)
oOutline.style.display = "none";
apf.dragMode = false;
if (!cancel && _self.dispatchEvent)
_self.dispatchEvent("afterresize", {
l: l, t: t, w: w+hordiff, h: h+verdiff
});
l = t = w = h = null;
};
return false;
}
function updateProperties(left, top, width, height, hdiff, vdiff, right, bottom) {
if (typeof left == "undefined") {
left = l, top = t, width = w, height = h,
vdiff = verdiff, hdiff = hordiff;
}
else posAbs = true;
var hasLeft = _self.left || _self.left === 0;
var hasRight = _self.right || _self.right === 0;
var hasBottom = _self.bottom || _self.bottom === 0;
var hasTop = _self.top || _self.top === 0;
if (posAbs) {
var htmlNode = (oOutline && oOutline.style.display == "block")
? oOutline
: _self.$ext;
if (hasRight && !(right || right === 0))
right = apf.getHtmlRight(htmlNode);
if (hasBottom && !(bottom || bottom === 0))
bottom = apf.getHtmlBottom(htmlNode);
if (hasRight) {
_self.setProperty("right", right, 0, _self.editable);
if (!_self.left)
htmlNode.style.left = "";
}
if (hasBottom) {
_self.setProperty("bottom", bottom, 0, _self.editable);
if (!_self.top)
htmlNode.style.top = "";
}
if ((left || left === 0) && (!hasRight || hasLeft))
_self.setProperty("left", left, 0, _self.editable);
if ((top || top === 0) && (!hasBottom || hasTop))
_self.setProperty("top", top, 0, _self.editable);
}
if (hdiff != undefined && width && (!hasLeft || !hasRight))
_self.setProperty("width", width + hdiff, 0, _self.editable)
if (vdiff != undefined && height && (!hasTop || !hasBottom))
_self.setProperty("height", height + vdiff, 0, _self.editable);
}
this.$updateProperties = updateProperties;
var min = Math.min, max = Math.max, lastTime, timer;
function resizeMove(e) {
if (!e) e = event;
//if (!e.button)
//return this.onmouseup();
// usability rule: start dragging ONLY when mouse pointer has moved delta x pixels
/*var dx = e.clientX - oX,
dy = e.clientY - oY,
distance;
if (!overThreshold
&& (distance = dx*dx > dy*dy ? dx : dy) * distance < 4)
return;*/
clearTimeout(timer);
if (lastTime && new Date().getTime()
- lastTime < (resizeOutline ? 6 : apf.mouseEventBuffer)) {
var z = {
clientX: e.clientX,
clientY: e.clientY
}
timer = $setTimeout(function(){
doResize(z);
}, 10);
return;
}
lastTime = new Date().getTime();
doResize(e);
if (_self.dispatchEvent)
_self.dispatchEvent("resize");
//overThreshold = true;
}
function doResize(e, force) {
var oHtml = resizeOutline && !force
? oOutline
: _self.$ext;
var sLeft = document.documentElement.scrollLeft,
sTop = document.documentElement.scrollTop;
if (we) {
if (posAbs)
oHtml.style.left = (l = max((lMin - _self.maxwidth),
min((lMax - _self.minwidth),
myPos[0] + e.clientX - oX + sLeft))) + "px";
oHtml.style.width = (w = min(_self.maxwidth - hordiff,
max(hordiff, _self.minwidth,
startPos[2] - (e.clientX - oX) + sLeft
) - hordiff)) + "px"; //@todo
}
if (no) {
if (posAbs)
oHtml.style.top = (t = max((tMin - _self.maxheight),
min((tMax - _self.minheight),
myPos[1] + e.clientY - oY + sTop))) + "px";
oHtml.style.height = (h = min(_self.maxheight - verdiff,
max(verdiff, _self.minheight,
startPos[3] - (e.clientY - oY) + sTop
) - verdiff)) + "px"; //@todo
}
if (ea)
oHtml.style.width = (w = min(_self.maxwidth - hordiff,
max(hordiff, _self.minwidth,
e.clientX - startPos[0] + (startPos[2] - rX) + sLeft)
- hordiff)) + "px";
if (so)
oHtml.style.height = (h = min(_self.maxheight - verdiff,
max(verdiff, _self.minheight,
e.clientY - startPos[1] + (startPos[3] - rY) + sTop)
- verdiff)) + "px";
//@todo apf3.0 this is execution wise inefficient
if (_self.parentNode && _self.parentNode.localName == "table") {
updateProperties();
apf.layout.processQueue();
}
if (_self.realtime) {
var change = _self.$stick = {};
//@todo calc l and t once at start of resize (subtract borders)
_self.$showResize(l || apf.getHtmlLeft(oHtml), t || apf.getHtmlTop(oHtml),
w && w + hordiff || oHtml.offsetWidth,
h && h + verdiff || oHtml.offsetHeight, e, change, we, no, ea, so);
if (posAbs && we && typeof change.l != "undefined")
oHtml.style.left = (l = max((lMin - _self.maxwidth), min((lMax - _self.minwidth), change.l))) + "px";
if (posAbs && no && typeof change.t != "undefined")
oHtml.style.top = (t = max((tMin - _self.maxheight), min((tMax - _self.minheight), change.t))) + "px";
if (typeof change.w != "undefined")
oHtml.style.width = (w = min(_self.maxwidth - hordiff,
max(hordiff, _self.minwidth,
change.w) - hordiff)) + "px";
if (typeof change.h != "undefined")
oHtml.style.height = (h = min(_self.maxheight - verdiff,
max(verdiff, _self.minheight,
change.h) - verdiff)) + "px";
}
if (apf.hasSingleRszEvent)
apf.layout.forceResize(_self.$int);
}
function getResizeType(x, y) {
var cursor = "",
tcursor = "";
posAbs = "absolute|fixed".indexOf(apf.getStyle(_self.$ext, "position")) > -1;
if (_self.resizable == "true" || _self.resizable == "vertical" || _self.resizable.indexOf('top') > -1 || _self.resizable.indexOf('bottom') > -1) {
if (y < rszborder + marginBox[0] && _self.resizable.indexOf('bottom') == -1)
cursor = posAbs ? "n" : "";
else if (y > this.offsetHeight - (rszcorner || rszborder) && _self.resizable.indexOf('top') == -1) //marginBox[0] - marginBox[2] -
cursor = "s";
}
if (_self.resizable == "true" || _self.resizable == "horizontal" || _self.resizable.indexOf('left') > -1 || _self.resizable.indexOf('right') > -1) {
if (x < (cursor ? rszcorner : rszborder) + marginBox[0] && _self.resizable.indexOf('right') == -1)
cursor += tcursor + (posAbs ? "w" : "");
else if (x > this.offsetWidth - (cursor || tcursor ? rszcorner : rszborder) && _self.resizable.indexOf('left') == -1) //marginBox[1] - marginBox[3] -
cursor += tcursor + "e";
}
return cursor;
}
var originalCursor;
function resizeIndicate(e) {
if (!e) e = event;
if (!_self.resizable || _self.editable || document.onmousemove)
return;
//@todo This is probably not gen purpose
var pos = apf.getAbsolutePosition(_self.$ext),//, _self.$ext.offsetParent
sLeft = 0,
sTop = 0,
x = e.clientX - pos[0] + sLeft + document.documentElement.scrollLeft,
y = e.clientY - pos[1] + sTop + document.documentElement.scrollTop;
if (!originalCursor)
originalCursor = apf.getStyle(this, "cursor");
var cursor = getResizeType.call(_self.$ext, x, y);
this.style.cursor = cursor
? getCssCursor(cursor) + "-resize"
: originalCursor || "default";
};
function getCssCursor(cursor) {
var cssCursor = cursor;
if (apf.isWebkit) {
if (cursor == "se" || cursor == "nw")
cssCursor = "nwse";
else if (cursor == "sw" || cursor == "ne")
cssCursor = "nesw";
else if (cursor == "s" || cursor == "n")
cssCursor = "ns";
else if (cursor == "e" || cursor == "w")
cssCursor = "ew";
}
return cssCursor;
}
var oOutline;
/*this.addEventListener("DOMNodeRemovedFromDocument", function(e) {
oOutline.refCount--;
if (!oOutline.refCount) {
//destroy
}
});*/
};
apf.GuiElement.propHandlers["resizable"] = function(value) {
this.implement(apf.Interactive);
this.$propHandlers["resizable"].apply(this, arguments);
}
apf.GuiElement.propHandlers["draggable"] = function(value) {
this.implement(apf.Interactive);
this.$propHandlers["draggable"].apply(this, arguments);
};
apf.Init.run("interactive");
apf.__MEDIA__ = 1 << 20;
apf.__MULTICHECK__ = 1 << 22;
/**
* All elements inheriting from this {@link term.baseclass baseclass} have checkable items.
*
* @class apf.MultiCheck
* @baseclass
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 3.0
*
*
*/
// @todo type detection, errors (see functions in multiselect)
apf.MultiCheck = function(){
this.$regbase = this.$regbase | apf.__MULTICHECK__;
// *** Properties *** //
this.multicheck = true;
this.checklength = 0;
this.$checkedList = [];
// *** Public Methods *** //
/**
* @event beforecheck Fires before a check is made
* @param {Object} e The standard event object, with the following properties:
* - `xmlNode` ([[XMLElement]]): the {@link term.datanode data node} that will be checked.
*/
/**
* @event aftercheck Fires after a check is made
* @param {Object} e The standard event object, with the following properties:
* - `xmlNode` ([[XMLElement]]): the {@link term.datanode data node} that was checked.
*
*/
/**
* Checks a single, or a set of, elements.
*
* The checking can be visually represented in this element.
* The element can be checked, partialy checked, or unchecked
*
* @param {Mixed} xmlNode The identifier to determine the selection.
* @return {Boolean} Indicates whether the selection could not be made (`false`)
*/
this.check = function(xmlNode, userAction) {
if (userAction && this.disabled
|| this.$checkedList.indexOf(xmlNode) > -1)
return;
if (userAction
&& this.$executeSingleValue("check", "checked", xmlNode, "true") !== false)
return;
if (this.dispatchEvent("beforecheck", {xmlNode : xmlNode}) === false)
return false;
if (!this.multicheck && this.$checkedList.length)
this.clearChecked(true);
this.$checkedList.push(xmlNode);
this.$setStyleClass(apf.xmldb.getHtmlNode(xmlNode, this),
"checked", ["partial"]);
// this.dispatchEvent("aftercheck", {
// list : this.$checkedList,
// xmlNode : xmlNode
// });
};
/**
* @event beforeuncheck Fires before a uncheck is made
* @param {Object} e The standard event object, with the following properties:
* - `xmlNode` ([[XMLElement]]): the {@link term.datanode data node} that will be unchecked.
*
*/
/**
* @event afteruncheck Fires after a uncheck is made
* @param {Object} e The standard event object, with the following properties:
* - `xmlNode` ([[XMLElement]]): the {@link term.datanode data node} that was unchecked.
*
*/
/**
* Unchecks a single, or set of, elements.
*
* @param {Mixed} xmlNode The identifier to determine the selection.
* @return {Boolean} Indicates if the selection could be made (`false`)
*/
this.uncheck = function(xmlNode, userAction) {
if (userAction && this.disabled
|| this.$checkedList.indexOf(xmlNode) == -1)
return;
if (userAction
&& this.$executeSingleValue("check", "checked", xmlNode, "false") !== false)
return;
if (this.dispatchEvent("beforeuncheck", {
xmlNode: xmlNode
}) === false)
return false;
this.$checkedList.remove(xmlNode);
this.$setStyleClass(apf.xmldb.getHtmlNode(xmlNode, this),
"", ["checked", "partial"]);
this.dispatchEvent("afteruncheck", {
list: this.$checkedList,
xmlNode: xmlNode
});
};
/**
* Toggles between a check and uncheck of a single, or set of, elements.
*
* @param {Mixed} xmlNode The identifier to determine the selection.
*
*/
this.checkToggle = function(xmlNode, userAction) {
if (userAction && this.disabled)
return;
if (xmlNode.style) {
var htmlNode = xmlNode,
id = htmlNode.getAttribute(apf.xmldb.htmlIdTag);
while (!id && htmlNode.parentNode)
id = (htmlNode = htmlNode.parentNode)
.getAttribute(apf.xmldb.htmlIdTag);
xmlNode = apf.xmldb.getNode(htmlNode)
}
if (this.$checkedList.indexOf(xmlNode) > -1)
this.uncheck(xmlNode, userAction);
else
this.check(xmlNode, userAction);
};
/**
* Checks a set of items.
*
* @param {Array} xmlNodeList The {@link term.datanode data nodes} that will be selected.
* @param {Boolean} uncheck If `true`, checks the items
* @param {Boolean} noClear If `true`, does not also clears the selection
* @param {Boolean} noEvent Indicates whether to call any events
* @event beforecheck Fires before a check is made
* object:
*
* @event aftercheck Fires after a check is made
* object:
* {XMLElement} xmlNode the {@link term.datanode data node} that is deselected.
*/
this.checkList = function(xmlNodeList, uncheck, noClear, noEvent, userAction) {
if (!xmlNodeList.indexOf)
xmlNodeList = apf.getArrayFromNodelist(xmlNodeList);
//@todo is this need for ie8 and/or other browsers
if (userAction) {
if (this.disabled)
return;
var changes = [];
for (var c, i = 0; i < xmlNodeList.length; i++) {
c = this.$executeSingleValue("check", "checked", xmlNodeList[i], uncheck ? "false" : "true", true)
if (c === false) break;
changes.push(c);
}
if (changes.length) {
return this.$executeAction("multicall", changes, "checked",
xmlNodeList[0], null, null,
xmlNodeList.length > 1 ? xmlNodeList : null);
}
}
if (userAction && this.disabled) return;
if (!noEvent && this.dispatchEvent("beforecheck", {
list: xmlNodeList
}) === false)
return false;
if (!uncheck && !noClear)
this.clearChecked(true);
if (!this.multicheck)
xmlNodeList = [xmlNodeList[0]];
var i;
if (uncheck) {
for (i = xmlNodeList.length - 1; i >= 0; i--) {
this.$checkedList.remove(xmlNodeList[i]);
this.$setStyleClass(
apf.xmldb.getHtmlNode(xmlNodeList[i], this), "", ["checked"]);
}
}
else {
for (i = xmlNodeList.length - 1; i >= 0; i--) {
this.$checkedList.push(xmlNodeList[i]);
this.$setStyleClass(
apf.xmldb.getHtmlNode(xmlNodeList[i], this), "checked");
}
}
if (!noEvent)
this.dispatchEvent("aftercheck", {
list: xmlNodeList
});
};
/**
* Removes the selection of one or more checked nodes.
*
* @param {Boolean} [noEvent] Indicates whether to call any events
*/
this.clearChecked = function(noEvent) {
if (!noEvent && this.dispatchEvent("beforeuncheck", {
xmlNode: this.$checkedList
}) === false)
return false;
for (var i = this.$checkedList.length - 1; i >= 0; i--) {
this.$setStyleClass(
apf.xmldb.getHtmlNode(this.$checkedList[i], this), "", ["checked"]);
}
this.$checkedList.length = 0;
if (!noEvent) {
this.dispatchEvent("afteruncheck", {
list: this.$checkedList
});
}
};
/**
* Determines whether a node is checked.
*
* @param {XMLElement} xmlNode The {@link term.datanode data node} to be checked.
* @return {Boolean} Whether the element is selected.
*/
this.isChecked = function(xmlNode) {
return this.$checkedList.indexOf(xmlNode) > -1;
};
/**
* Retrieves an array or a document fragment containing all the checked
* {@link term.datanode data nodes} from this element.
*
* @param {Boolean} [xmldoc] Specifies whether the method should return a document fragment.
* @return {Mixed} The selection of this element.
*/
this.getChecked = function(xmldoc) {
var i, r;
if (xmldoc) {
r = this.xmlRoot
? this.xmlRoot.ownerDocument.createDocumentFragment()
: apf.getXmlDom().createDocumentFragment();
for (i = 0; i < this.$checkedList.length; i++)
apf.xmldb.cleanNode(r.appendChild(
this.$checkedList[i].cloneNode(true)));
}
else {
for (r = [], i = 0; i < this.$checkedList.length; i++)
r.push(this.$checkedList[i]);
}
return r;
};
/**
* Checks all the {@link term.eachnode each nodes} of this element
*
*/
this.checkAll = function(userAction) {
if (!this.multicheck || userAction && this.disabled || !this.xmlRoot)
return;
var nodes = this.$isTreeArch
? this.xmlRoot.selectNodes(".//"
+ this.each.split("|").join("|.//"))
: this.getTraverseNodes();
this.checkList(nodes);
};
this.addEventListener("beforeload", function(){
if (!this.$hasBindRule("checked")) //only reset state when check state isnt recorded
this.clearChecked(true);
});
this.addEventListener("afterload", function(){
if (!this.$hasBindRule("checked") && this.$checkedList.length) //only reset state when check state isnt recorded
this.checkList(this.$checkedList, false, true, false); //@todo could be optimized (no event calling)
});
this.addEventListener("xmlupdate", function(e) {
if (e.action == "attribute" || e.action == "text"
|| e.action == "synchronize" || e.action == "update") {
//@todo list support!
var c1 = apf.isTrue(this.$applyBindRule("checked", e.xmlNode));
var c2 = this.isChecked(e.xmlNode);
if (c1 != c2) {
if (c1) {
this.check(e.xmlNode);
}
else {
this.uncheck(e.xmlNode);
}
}
}
});
this.addEventListener("aftercheck", function(){
//@todo inconsistent because setting this is in event callback
if (this.checklength != this.$checkedList.length)
this.setProperty("checklength", this.$checkedList.length);
});
this.addEventListener("afteruncheck", function(){
//@todo inconsistent because setting this is in event callback
if (this.checklength != this.$checkedList.length)
this.setProperty("checklength", this.$checkedList.length);
});
};
apf.__TRANSACTION__ = 1 << 3;
apf.__VIRTUALVIEWPORT__ = 1 << 19;
apf.__XFORMS__ = 1 << 17;
/**
* Object representing the window of the AML application. The semantic is
* similar to that of a window in the browser, except that this window is not
* the same as the JavaScript global object. It handles the focussing within
* the document and several other events such as exit and the keyboard events.
*
* @class apf.window
* @inherits apf.Class
* @default_private
*
* @author Ruben Daniels (ruben AT ajax DOT org)
* @version %I%, %G%
* @since 0.8
*/
/**
* @event blur Fires when the browser window loses focus.
*/
/**
* @event focus Fires when the browser window receives focus.
*
*
*/
apf.window = function(){
this.$uniqueId = apf.all.push(this);
this.apf = apf;
/**
* Returns a string representation of this object.
*/
this.toString = function(){
return "[apf.window]";
};
/**
* Retrieves the primary {@link apf.actiontracker action tracker} of the application.
*/
this.getActionTracker = function(){
return this.$at
};
/*
* @private
*/
this.loadCodeFile = function(url) {
//if(apf.isWebkit) return;
if (self[url])
apf.importClass(self[url], true, this.win);
else
apf.include(url);//, this.document);
};
/**
* Show the browser window.
*/
this.show = function(){
if (apf.isDeskrun)
jdwin.Show();
};
/**
* Hide the browser window.
*/
this.hide = function(){
if (apf.isDeskrun) {
jdwin.Hide();
}
else {
this.loaded = false;
if (this.win)
this.win.close();
}
};
/**
* Focus the browser window.
*/
this.focus = function(){
if (apf.isDeskrun)
jdwin.SetFocus();
else
window.focus();
};
/**
* Set the icon of the browser window.
* @param {String} url The location of the _.ico_ file.
*/
this.setIcon = function(url) {
if (apf.isDeskrun)
jdwin.icon = parseInt(url) == url ? parseInt(url) : url;
};
/**
* Set the title of the browser window.
* @param {String} value The new title of the window.
*/
this.setTitle = function(value) {
this.title = value || "";
if (apf.isDeskrun)
jdwin.caption = value;
else
document.title = (value || "");
};
/*
* @private
*/
this.loadAml = function(x) {
if (x[apf.TAGNAME] == "deskrun")
this.loadDeskRun(x);
/*else {
}*/
};
// *** Focus Internals *** //
this.vManager = new apf.visibilitymanager();
this.zManager = new apf.zmanager();
this.$tabList = [];
this.$addFocus = function(amlNode, tabindex, isAdmin) {
if (!isAdmin) {
amlNode.addEventListener("DOMNodeInserted", moveFocus);
amlNode.addEventListener("DOMNodeRemoved", removeFocus);
if (amlNode.$isWindowContainer > -2) {
amlNode.addEventListener("focus", trackChildFocus);
amlNode.addEventListener("blur", trackChildFocus);
amlNode.$focusParent = amlNode;
if (amlNode.$isWindowContainer > -1) {
if (!amlNode.$tabList)
amlNode.$tabList = [amlNode];
this.$tabList.push(amlNode);
return;
}
else {
amlNode.$tabList = [amlNode];
}
}
}
var fParent = findFocusParent(amlNode),
list = fParent.$tabList;
if (!amlNode.$isWindowContainer)
amlNode.$focusParent = fParent;
else
amlNode.$focusParent2 = fParent;
if (list[tabindex])
list.insertIndex(amlNode, tabindex);
else if (tabindex || parseInt(tabindex) === 0)
list[tabindex] = amlNode;
else
list.push(amlNode);
};
this.$removeFocus = function(amlNode) {
if (!amlNode.$focusParent)
return;
amlNode.$focusParent.$tabList.remove(amlNode);
if (!amlNode.$isWindowContainer) {
amlNode.removeEventListener("DOMNodeInserted", moveFocus);
amlNode.removeEventListener("DOMNodeRemoved", removeFocus);
}
if (amlNode.$isWindowContainer > -2) {
amlNode.removeEventListener("focus", trackChildFocus);
amlNode.removeEventListener("blur", trackChildFocus);
}
};
var focusLoopDetect;
this.$focus = function(amlNode, e, force) {
var aEl = this.activeElement;
if (aEl == amlNode && !force)
return; //or maybe when force do $focus
this.$settingFocus = amlNode;
if (!e)
e = {};
e.toElement = amlNode;
e.fromElement = aEl;
if (aEl && aEl != amlNode && focusLoopDetect != aEl) {
focusLoopDetect = aEl;
aEl.blur(true, e);
if (focusLoopDetect != aEl)
return false;
}
if (amlNode.$focussable != apf.MENU || !apf.activeElement) {
apf.activeElement =
this.document.activeElement =
this.document.documentElement.$lastFocussed = amlNode;
}
(apf.window.activeElement = amlNode).focus(true, e);
this.$settingFocus = null;
apf.dispatchEvent("movefocus", {
toElement: amlNode
});
};
this.$blur = function(amlNode) {
var aEl = this.activeElement;
if (aEl != amlNode)
return false;
aEl.$focusParent.$lastFocussed = null;
if (aEl.$focussable != apf.MENU) {
apf.activeElement =
this.document.activeElement = null;
}
apf.window.activeElement = null;
apf.dispatchEvent("movefocus", {
fromElement: amlNode
});
};
var lastFocusParent;
this.$focusDefault = function(amlNode, e) {
var fParent = findFocusParent(amlNode);
this.$focusLast(fParent, e);
};
this.$focusRoot = function(e) {
var docEl = apf.document.documentElement;
if (this.$focusLast(docEl, e) === false) {
//docEl.$lastFocussed = null;
//this.moveNext(null, apf.document.documentElement, true, e);
}
};
this.$focusLast = function(amlNode, e, ignoreVisible) {
var lf = amlNode.$lastFocussed;
if (lf && lf.parentNode && lf.$focussable === true
&& (ignoreVisible || lf.$ext.offsetHeight)) {
this.$focus(lf, e, true);
}
else { //Let's find the object to focus first
var next, node = amlNode, skip;
while (node) {
if (!skip && node.focussable !== false && node.$focussable === true && !node.$tabList
&& (ignoreVisible || node.$ext && node.$ext.offsetHeight) && node.disabled < 1) {
this.$focus(node, e, true);
break;
}
//Walk sub tree
if ((next = !skip && node.firstChild || !(skip = false) && node.nextSibling)) {
node = next;
if (node.$isWindowContainer > 0)
skip = true;
}
else if (node == amlNode) {
if (node.$isWindowContainer)
this.$focus(node, e, true);
return;
}
else {
do {
node = node.parentNode;
} while (node && !node.nextSibling && node != amlNode
&& !node.$isWindowContainer)
if (node == amlNode) {
if (node.$isWindowContainer)
this.$focus(node, e, true);
return; //do nothing
}
if (node) {
if (node.$isWindowContainer) {
this.$focus(node, e, true);
break;
}
node = node.nextSibling;
}
}
}
if (!node)
this.$focus(apf.document.documentElement);//return false;//
/*@todo get this back from SVN
var node, list = amlNode.$tabList;
for (var i = 0; i < list.length; i++) {
node = list[i];
if (node.focussable !== false && node.$focussable === true
&& (ignoreVisible || node.$ext.offsetHeight)) {
this.$focus(node, e, true);
return;
}
}
this.$focus(apf.document.documentElement);*/
}
};
function trackChildFocus(e) {
if (e.name == "blur") {
if (e.srcElement != this && this.$blur)
this.$blur();
return;
}
if (e.srcElement != this && this.$focus && (!e || !e.mouse || this.$focussable == apf.KEYBOARD_MOUSE))
this.$focus();
if (e.srcElement == this || e.trackedChild) {
e.trackedChild = true;
return;
}
this.$lastFocussed = e.srcElement;
if (this.localName && this.localName.indexOf("window") > -1)
e.trackedChild = true;
}
function findFocusParent(amlNode) {
var node = amlNode;
do {
node = node.parentNode;
} while (node && !node.$isWindowContainer);
//(!node.$focussable || node.focussable === false)
return node || apf.document.documentElement;
}
//Dom handler
//@todo make this look at the dom tree insertion point to determine tabindex
function moveFocus(e) {
if (e && e.currentTarget != this)
return;
if (this.$isWindowContainer)
apf.window.$tabList.pushUnique(this);
else
apf.window.$addFocus(this, this.tabindex, true)
}
//Dom handler
function removeFocus(e) {
if (e && (e.currentTarget != this || e.$doOnlyAdmin))
return;
//@todo apf3.0 this should be fixed by adding domremovenode events to all children
var list = this.$focusParent.$tabList;
var nodes = this.childNodes;
if (nodes) {
for (var i = 0, l = nodes.length; i < l; i++) {
list.remove(nodes[i]); //@todo assuming no windows here
}
}
if (apf.window.activeElement == this)
apf.window.moveNext();
if (this.$isWindowContainer) {
apf.window.$tabList.remove(this); //@todo this can't be right
return;
}
if (!this.$focusParent)
return;
list.remove(this);
//this.$focusParent = null; //@experimental to not execute this
}
// *** Focus API *** //
/**
* Determines whether a given AML element has the focus.
* @param {apf.AmlElement} The element to check
* @returns {Boolean} Indicates whether the element has focus.
*/
this.hasFocus = function(amlNode) {
return this.activeElement == amlNode;
};
/*
* @private
*/
this.moveNext = function(shiftKey, relObject, switchWindows, e) {
if (switchWindows && apf.window.activeElement) {
var p = apf.window.activeElement.$focusParent;
if (p.visible && p.modal)
return false;
}
var dir, start, next,
amlNode = relObject || apf.window.activeElement,
fParent = amlNode
? (switchWindows && amlNode.$isWindowContainer
&& amlNode.$isWindowContainer != -1
? apf.window
: e && e.innerList ? amlNode.$focusParent : amlNode.$focusParent2 || amlNode.$focusParent)
: apf.document.documentElement,
list = fParent.$tabList;
if (amlNode && (switchWindows || amlNode != apf.document.documentElement)) {
start = (list || []).indexOf(amlNode);
if (start == -1) {
return;
}
}
else {
start = -1;
}
if (this.activeElement == amlNode
&& list.length == 1 || list.length == 0)
return false;
dir = (shiftKey ? -1 : 1);
next = start;
if (start < 0)
start = 0;
do {
next += dir;
if (next >= list.length)
next = 0;
else if (next < 0)
next = list.length - 1;
if (start == next && amlNode) {
if (list[0].$isWindowContainer)
this.$focus(list[0], e);
return false; //No visible enabled element was found
}
amlNode = list[next];
}
while (amlNode && (
amlNode.disabled > 0
|| amlNode == apf.window.activeElement
|| (switchWindows ? !amlNode.visible : amlNode.$ext && !amlNode.$ext.offsetHeight)
|| amlNode.focussable === false
|| switchWindows && !amlNode.$tabList.length
));
if (!amlNode)
return;
if (fParent == apf.window && amlNode.$isWindowContainer != -2) {
this.$focusLast(amlNode, {mouse:true}, switchWindows);
}
else {
(e || (e = {})).shiftKey = shiftKey;
this.$focus(amlNode, e);
}
};
/*
* @private
*/
this.focusDefault = function(){
if (this.moveNext() === false)
this.moveNext(null, apf.document.documentElement, true)
};
// *** Set Window Events *** //
apf.addListener(window, "beforeunload", function(){
return apf.dispatchEvent("exit");
});
//@todo apf3.x why is this loaded twice
apf.addListener(window, "unload", function(){
if (!apf)
return;
apf.window.isExiting = true;
});
// *** Keyboard and Focus Handling *** //
apf.addListener(document, "contextmenu", function(e) {
if (!e)
e = event;
var pos, ev,
amlNode = apf.findHost(e.srcElement || e.target)
|| apf.window.activeElement
|| apf.document && apf.document.documentElement;
if (amlNode && amlNode.localName == "menu") //The menu is already visible
return false;
//if (amlNode && amlNode.localName == "menu")
//amlNode = amlNode.parentNode;
if (apf.contextMenuKeyboard) {
if (amlNode) {
pos = amlNode.selected
? apf.getAbsolutePosition(amlNode.$selected)
: apf.getAbsolutePosition(amlNode.$ext || amlNode.$pHtmlNode);
}
else {
pos = [0, 0];
}
ev = {
x: pos[0] + 10 + document.documentElement.scrollLeft,
y: pos[1] + 10 + document.documentElement.scrollTop,
amlNode: amlNode,
htmlEvent: e
}
}
else {
if (e.htmlEvent) {
ev = e;
}
else {
ev = { //@todo probably have to deduct the border of the window
x: e.clientX + document.documentElement.scrollLeft,
y: e.clientY + document.documentElement.scrollTop,
htmlEvent: e
}
}
}
ev.bubbles = true; //@todo discuss this, are we ok with bubbling?
apf.contextMenuKeyboard = null;
if ((amlNode || apf).dispatchEvent("contextmenu", ev) === false
|| ev.returnValue === false) {
if (e.preventDefault)
e.preventDefault();
return false;
}
if (apf.config.disableRightClick) {
if (e.preventDefault)
e.preventDefault();
return false;
}
});
apf.addListener(document, "mouseup", function(e) {
if (!e) e = event;
apf.dispatchEvent("mouseup", {
htmlEvent: e
});
});
var ta = {"INPUT":1, "TEXTAREA":1, "SELECT":1, "EMBED":1, "OBJECT":1, "PRE": 1};
apf.addListener(document, "mousedown", this.$mousedown = function(e) {
if (!e) e = event;
var p,
amlNode = apf.findHost(e.srcElement || e.target);
/*cEditable = amlNode && amlNode.liveedit
;*/
apf.popup.$mousedownHandler(amlNode, e);
if (amlNode === false)
amlNode = apf.window.activeElement;
var eventTarget = amlNode;
while (amlNode && !amlNode.focussable)
amlNode = amlNode.parentNode;
//Make sure the user cannot leave a modal window
if ((!amlNode || ((!amlNode.$focussable || amlNode.focussable === false)
&& amlNode.canHaveChildren != 2 && !amlNode.$focusParent))
&& apf.config.allowBlur) {
lastFocusParent = null;
if (apf.window.activeElement)
apf.window.activeElement.blur();
}
else if (amlNode) { //@todo check this for documentElement apf3.0
if ((p = apf.window.activeElement
&& apf.window.activeElement.$focusParent || lastFocusParent)
&& p.visible && p.modal && amlNode.$focusParent != p
&& amlNode.$isWindowContainer != -1) {
apf.window.$focusLast(p, {mouse: true, ctrlKey: e.ctrlKey});
}
else if (!amlNode && apf.window.activeElement) {
apf.window.$focusRoot();
}
else if (amlNode.$isWindowContainer == -1) {
if (amlNode.$tabList.length)
apf.window.moveNext(null, amlNode.$tabList[0], null, {mouse: true, innerList: true});
else
apf.window.$focus(amlNode);
}
else if ((amlNode.disabled == undefined || amlNode.disabled < 1)
&& amlNode.focussable !== false) {
if (amlNode.$focussable) { // === apf.KEYBOARD_MOUSE
apf.window.$focus(amlNode, {mouse: true, ctrlKey: e.ctrlKey});
}
else if (amlNode.canHaveChildren == 2) {
if (!apf.config.allowBlur || !apf.window.activeElement
|| apf.window.activeElement.$focusParent != amlNode)
apf.window.$focusLast(amlNode, {mouse: true, ctrlKey: e.ctrlKey});
}
// else {
// if (!apf.config.allowBlur || amlNode != apf.document.documentElement)
// apf.window.$focusDefault(amlNode, {mouse: true, ctrlKey: e.ctrlKey});
// }
}
else {
// Disabled this to prevent menus from becoming unclickable
// apf.window.$focusDefault(amlNode, {mouse: true, ctrlKey: e.ctrlKey});
}
}
amlNode = eventTarget;
apf.dispatchEvent("mousedown", {
htmlEvent: e,
amlNode: amlNode || apf.document.documentElement
});
//Non IE/ iPhone selection handling
if (apf.isIE || apf.isIphone)
return;
var canSelect = !((!apf.document
&& (!apf.isParsingPartial || amlNode)
|| apf.dragMode) && !ta[e.target && e.target.tagName]);
if (canSelect && amlNode) {
if (!e.target && e.srcElement)
e.target = {};
var isTextInput = (ta[e.target.tagName]
|| e.target.contentEditable == "true") && !e.target.disabled //@todo apf3.0 need to loop here?
|| amlNode.$isTextInput
&& amlNode.$isTextInput(e) && amlNode.disabled < 1;
//(!amlNode.canHaveChildren || !apf.isChildOf(amlNode.$int, e.srcElement))
if (!apf.config.allowSelect && !isTextInput
&& amlNode.nodeType != amlNode.NODE_PROCESSING_INSTRUCTION
&& !amlNode.textselect) //&& (!amlNode.$int || amlNode.$focussable) //getElementsByTagNameNS(apf.ns.xhtml, "*").length
canSelect = false;
}
if (amlNode && amlNode.name === "editor::ace") {
canSelect = true;
}
if (!canSelect && e.button != 2) { // && !cEditable
if (e.preventDefault)
e.preventDefault();
try{
if (document.activeElement && document.activeElement.contentEditable == "true") //@todo apf3.0 need to loop here?
document.activeElement.blur();
}catch(e) {}
}
});
//IE selection handling
apf.addListener(document, "selectstart", function(e) {
if (!apf.isIE)
return;
if (!e) e = event;
var amlNode = apf.findHost(e.srcElement);
var canSelect = !(!apf.document
&& (!apf.isParsingPartial || amlNode)
|| apf.dragMode);
if (canSelect) {
//(!amlNode.canHaveChildren || !apf.isChildOf(amlNode.$int, e.srcElement))
if (!apf.config.allowSelect
&& (amlNode && amlNode.nodeType != amlNode.NODE_PROCESSING_INSTRUCTION
&& !amlNode.textselect)) //&& !amlNode.$int // getElementsByTagNameNS(apf.ns.xhtml, "*").length
canSelect = false;
}
if (!canSelect) {
e.returnValue = false;
return false;
}
});
// Keyboard forwarding to focussed object
apf.addListener(document, "keyup", this.$keyup = function(e) {
if (!e) e = event;
var ev = {
keyCode: e.keyCode,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
metaKey: e.metaKey,
altKey: e.altkey,
htmlEvent: e,
bubbles: true //@todo is this much slower?
};
var aEl = apf.document && apf.window.activeElement;
if ((aEl && !aEl.disableKeyboard
? aEl.dispatchEvent("keyup", ev)
: apf.dispatchEvent("keyup", ev)) === false) {
apf.preventDefault(e);
return false;
}
});
var wheel = this.$mousewheel = function wheel(e) {
if (!e)
e = event;
var delta = null;
if (e.wheelDelta) {
delta = e.wheelDelta / 120;
if (apf.isOpera)
delta *= -1;
}
else if (e.detail) {
delta = -e.detail / 3;
}
if (delta !== null) {
var ev = {
delta: delta,
target: e.target || e.srcElement,
button: e.button,
ctrlKey: e.ctrlKey,
shiftKey: e.shiftKey,
metaKey: e.metaKey,
altKey: e.altKey,
bubbles: true,
htmlEvent: e
};
var amlNode = apf.findHost(e.srcElement || e.target);
var res = (amlNode || apf).dispatchEvent("mousescroll", ev);
if (res === false || ev.returnValue === false) {
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
}
}
}
if (document.addEventListener)
document.addEventListener('DOMMouseScroll', wheel, false);
window.onmousewheel =
document.onmousewheel = wheel; //@todo 2 keer events??
//var browserNavKeys = {32:1,33:1,34:1,35:1,36:1,37:1,38:1,39:1,40:1}
var keyPressed = false;
apf.addListener(window, "blur", function(e) {
keyPressed = false;
})
apf.addListener(document, "keyup", function(e) {
e = e || event;
if (!keyPressed)
return;
keyPressed = false;
if (e.ctrlKey && e.keyCode == 9 && apf.window.activeElement) {
var w = apf.window.activeElement.$focusParent;
if (w.modal) {
if (e.preventDefault)
e.preventDefault();
return false;
}
// todo is there better way to prevent blur on ctrl-tab?
if (apf.activeElement && apf.activeElement.editor)
return;
apf.window.moveNext(e.shiftKey,
apf.window.activeElement.$focusParent, true);
w = apf.window.activeElement.$focusParent;
if (w && w.bringToFront)
w.bringToFront();
if (e.preventDefault)
e.preventDefault();
return false;
}
});
//@todo optimize this function
apf.addListener(document, "keydown", this.$keydown = function(e) {
e = e || event;
keyPressed = true;
if (e.keyCode == 93)
apf.contextMenuKeyboard = true;
var amlNode = apf.window.activeElement, //apf.findHost(e.srcElement || e.target),
htmlNode = (e.explicitOriginalTarget || e.srcElement || e.target),
isTextInput = (ta[htmlNode.tagName]
|| htmlNode.contentEditable == "true" || htmlNode.contentEditable == "plaintext-only")
&& !htmlNode.disabled
|| amlNode && amlNode.$isTextInput
&& amlNode.$isTextInput(e) && amlNode.disabled < 1;
var eInfo = {
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
altKey: e.altKey,
keyCode: e.keyCode,
htmlEvent: e,
isTextInput: isTextInput,
bubbles: true
};
delete eInfo.currentTarget;
//Keyboard forwarding to focussed object
var aEl = amlNode; //isTextInput ? amlNode :
if ((aEl && !aEl.disableKeyboard && !aEl.editable
? aEl.dispatchEvent("keydown", eInfo)
: apf.dispatchEvent("keydown", eInfo)) === false) {
apf.stopEvent(e);
if (apf.canDisableKeyCodes) {
try {
e.keyCode = 0;
}
catch (e) {}
}
return false;
}
//Focus handling
else if ((!apf.config.disableTabbing || apf.window.activeElement) && e.keyCode == 9) {
//Window focus handling
if (e.ctrlKey && apf.window.activeElement) {
var w = apf.window.activeElement.$focusParent;
if (w.modal) {
if (e.preventDefault)
e.preventDefault();
return false;
}
apf.window.moveNext(e.shiftKey,
apf.window.activeElement.$focusParent, true);
w = apf.window.activeElement.$focusParent;
if (w && w.bringToFront)
w.bringToFront();
}
//Element focus handling
else if (!apf.window.activeElement || apf.window.activeElement.tagName != "menu") {
apf.window.moveNext(e.shiftKey);
}
if (e.preventDefault)
e.preventDefault();
return false;
}
//Disable backspace behaviour triggering the backbutton behaviour
var altKey = apf.isMac ? e.metaKey : e.altKey;
if (apf.config.disableBackspace
&& e.keyCode == 8// || (altKey && (e.keyCode == 37 || e.keyCode == 39)))
&& !isTextInput) {
if (apf.canDisableKeyCodes) {
try {
e.keyCode = 0;
}
catch (e) {}
}
e.returnValue = false;
}
//Disable space behaviour of scrolling down the page
/*if(Application.disableSpace && e.keyCode == 32 && e.srcElement.tagName.toLowerCase() != "input"){
e.keyCode = 0;
e.returnValue = false;
}*/
//Disable F5 refresh behaviour
if (apf.config.disableF5 && (e.keyCode == 116 || e.keyCode == 117)) {
if (apf.canDisableKeyCodes) {
try {
e.keyCode = 0;
}
catch (e) {}
}
else {
e.preventDefault();
e.stopPropagation();
}
//return false;
}
/*if (browserNavKeys[e.keyCode] && apf.window.activeElement
&& apf.config.autoDisableNavKeys)
e.returnValue = false;*/
if (e.keyCode == 27)
e.returnValue = false;
if (!apf.config.allowSelect
&& e.shiftKey && (e.keyCode > 32 && e.keyCode < 41)
&& !isTextInput) {
e.returnValue = false;
}
//apf.dispatchEvent("keydown", null, eInfo);
if (e.returnValue === false && e.preventDefault)
e.preventDefault();
return e.returnValue;
});
apf.document = {};
this.init = function(strAml) {
if (apf.actiontracker) {
this.$at = new apf.actiontracker();
this.$at.name = "default";
apf.nameserver.register("actiontracker", "default", this.$at);
}
//Put this in callback in between the two phases
this.$domParser = new apf.DOMParser();
this.document = apf.document = this.$domParser.parseFromString(strAml,
"text/xml", {
timeout: apf.config.initdelay,
callback: function(doc) {
//@todo apf3.0
//Call the onload event (prevent recursion)
if (apf.parsed != 2) {
//@todo apf3.0 onload is being called too often
var inital = apf.parsed;
apf.parsed = 2;
apf.dispatchEvent("parse", { //@todo apf3.0 document
initial: inital
});
apf.parsed = true;
}
if (!apf.loaded) {
//Set the default selected element
if (!apf.window.activeElement && (!apf.config.allowBlur
|| apf.document.documentElement
&& apf.document.documentElement.editable))
apf.window.focusDefault();
apf.loaded = true;
$setTimeout(function() {
apf.dispatchEvent("load");
apf.addEventListener("$event.load", function(cb) {
cb();
});
});
}
//END OF ENTIRE APPLICATION STARTUP
}
}); //async
};
/*
* @private
*/
this.destroy = function(){
};
};
apf.window.prototype = new apf.Class().$init();
apf.window = new apf.window();
/**
* Compatibility layer for Gecko based browsers.
* @private
*/
apf.runGecko = function(){
if (apf.runNonIe)
apf.runNonIe();
/* ***************************************************************************
XSLT
****************************************************************************/
//XMLDocument.selectNodes
HTMLDocument.prototype.selectNodes = XMLDocument.prototype.selectNodes = function(sExpr, contextNode) {
try {
var oResult = this.evaluate(sExpr, (contextNode || this),
this.createNSResolver(this.documentElement),
7, null); //XpathResult.ORDERED_NODE_ITERATOR_TYPE
}
catch (ex) {
var msg = ex.message;
if (ex.code == ex.INVALID_EXPRESSION_ERR)
msg = msg.replace(/the expression/i, "'" + sExpr + "'");
throw new Error(ex.lineNumber, "XPath error: " + msg);
}
var nodeList = new Array(oResult.snapshotLength);
nodeList.expr = sExpr;
for (var i = nodeList.length - 1; i >= 0; i--)
nodeList[i] = oResult.snapshotItem(i);
return nodeList;
};
//Element.selectNodes
Text.prototype.selectNodes =
Attr.prototype.selectNodes =
Element.prototype.selectNodes = function(sExpr) {
return this.ownerDocument.selectNodes(sExpr, this);
};
//XMLDocument.selectSingleNode
HTMLDocument.prototype.selectSingleNode =
XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode) {
try {
var oResult = this.evaluate(sExpr, (contextNode || this),
this.createNSResolver(this.documentElement),
9, null); //XpathResult.FIRST_ORDERED_NODE_TYPE
}
catch (ex) {
var msg = ex.message;
if (ex.code == ex.INVALID_EXPRESSION_ERR)
msg = msg.replace(/the expression/i, "'" + sExpr + "'");
throw new Error(ex.lineNumber, "XPath error: " + msg);
}
return oResult.singleNodeValue;
};
//Element.selectSingleNode
Text.prototype.selectSingleNode =
Attr.prototype.selectSingleNode =
Element.prototype.selectSingleNode = function(sExpr) {
return this.ownerDocument.selectSingleNode(sExpr, this);
};
var serializer = new XMLSerializer();
var o = document.createElement("div");
apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) {
var frag, l, node, i;
if (nodeList) {
frag = document.createDocumentFragment();
for (i = nodeList.length - 1; i >= 0; i--) {
node = nodeList[i];
frag.insertBefore(node, frag.firstChild);
}
}
o.innerHTML = typeof s == "string" ? s : apf.html_entity_decode(serializer.serializeToString(frag))
.replace(/<([^>]+)\/>/g, "<$1>$1>");
frag = document.createDocumentFragment();
for (i = 0, l = o.childNodes.length; i < l; i++) {
node = o.childNodes[0];
frag.appendChild(node);
}
if (beforeNode)
htmlNode.insertBefore(frag, beforeNode);
htmlNode.appendChild(frag);
};
apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, s) {
if (htmlNode.nodeType != 11 && !htmlNode.style)
return htmlNode.appendChild(xmlNode);
if (!s) {
s = apf.html_entity_decode(xmlNode.serialize
? xmlNode.serialize(true)
: ((xmlNode.nodeType == 3 || xmlNode.nodeType == 4 || xmlNode.nodeType == 2)
? xmlNode.nodeValue
: serializer.serializeToString(xmlNode)));
}
o.innerHTML = s.replace(/<([^>]+)\/>/g, "<$1>$1>");
if (beforeNode)
htmlNode.insertBefore(o.firstChild, beforeNode);
else
htmlNode.appendChild(o.firstChild);
return beforeNode ? beforeNode.previousSibling : htmlNode.lastChild;
};
/* ******** Error Compatibility **********************************************
Error Object like IE
****************************************************************************/
function Error(nr, msg) {
this.message = msg;
this.nr = nr;
}
apf.getHtmlLeft = function(oHtml) {
return (oHtml.offsetLeft
+ (parseInt(apf.getStyle(oHtml.parentNode, "borderLeftWidth")) || 0));
};
apf.getHtmlRight = function(oHtml) {
var p;
return (((p = oHtml.offsetParent).tagName == "BODY"
? apf.getWindowWidth()
: p.offsetWidth)
- oHtml.offsetLeft - oHtml.offsetWidth
- (2 * (parseInt(apf.getStyle(p, "borderLeftWidth")) || 0))
- (parseInt(apf.getStyle(p, "borderRightWidth")) || 0));
};
apf.getHtmlTop = function(oHtml) {
return (oHtml.offsetTop
+ (parseInt(apf.getStyle(oHtml.parentNode, "borderTopWidth")) || 0));
};
apf.getHtmlBottom = function(oHtml) {
var p;
return (((p = oHtml.offsetParent).tagName == "BODY"
? apf.getWindowHeight()
: p.offsetHeight)
- oHtml.offsetTop - oHtml.offsetHeight
- (2 * (parseInt(apf.getStyle(p, "borderTopWidth")) || 0))
- (parseInt(apf.getStyle(p, "borderBottomWidth")) || 0));
};
apf.getBorderOffset = function(oHtml) {
return [-1 * (parseInt(apf.getStyle(oHtml, "borderLeftWidth")) || 0),
-1 * (parseInt(apf.getStyle(oHtml, "borderTopWidth")) || 0)];
};
};
/**
* Compatibility layer for Internet Explorer browsers.
* @private
*/
apf.runIE = function(){
apf.runWebkit();
// return;
var silent
HTMLDocument.prototype.sn = XMLDocument.prototype.sn = HTMLDocument.prototype.sn || HTMLDocument.prototype.selectNodes;
HTMLDocument.prototype.selectNodes = XMLDocument.prototype.selectNodes = function(sExpr, contextNode) {
if (/^\w+$/.test(sExpr) && contextNode) {
var all = contextNode.querySelectorAll(sExpr);
var nodeList = new Array(all.length);
for (var i = nodeList.length - 1; i >= 0; i--)
nodeList[i] = all[i];
return nodeList;
}
silent || console.log(sExpr, contextNode)
return this.sn(sExpr, contextNode)
};
HTMLDocument.prototype.sns = XMLDocument.prototype.sns = HTMLDocument.prototype.sns || HTMLDocument.prototype.selectSingleNode
HTMLDocument.prototype.selectSingleNode = XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode) {
var n = findNode(contextNode, sExpr);
if (sExpr.lastIndexOf("descendant-or-self::node()[@") == 0)
n = contextNode.querySelector(sExpr.replace("descendant-or-self::node()[@", "*["));
silent = true
try {var m = this.sns(sExpr, contextNode); } catch(e) {}
silent = !true
if (n != m && m) {
console.log(sExpr)
debugger
n = m
findNode(contextNode, sExpr);
}
return n
};
apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) {
var node, frag, a, i, l;
if (nodeList) {
frag = document.createElement("div");
a = [], i = 0, l = nodeList.length;
for (; i < l; i++) {
if (!(node = nodeList[i])) continue;
frag.appendChild(node);
}
}
(beforeNode || htmlNode).insertAdjacentHTML(beforeNode
? "beforebegin"
: "beforeend", s || (frag ? frag.innerHTML : "")
.replace(/<([^>]+)\/>/g, "<$1>$1>"));
};
};
function findNode(htmlNode, textNode, parts, maxRecur) {
if (!parts)
parts = textNode.split("/")
textNode = parts.shift()
if (textNode == ".") {
return htmlNode
}
if (textNode == "text()") {
var ch = htmlNode.childNodes;
for (var i = 0; i < ch.length; i++) {
if (ch[i].nodeType == 3 || ch[i].nodeType == 4)
return ch[i];
}
debugger
throw new Error("can't find node " + textNode);
} else if (textNode[0] == "@") {
var name = textNode.substr(1);
return htmlNode.getAttributeNode(name);
var value = htmlNode.getAttribute(name);
return {
name: name,
value: value,
nodeValue: value,
nodeType:2
};
} else {
var index = 0;
textNode = textNode.replace(/\[\d+\]/, function(x){
index = parseInt(x.slice(1, -1)) - 1;
return "";
});
var ch = htmlNode.childNodes;
for (var i = 0; i < ch.length; i++) {
if (ch[i].tagName && ch[i].tagName.toLowerCase() === textNode) {
if (index) index--;
else if (parts.length) return findNode(ch[i], "", parts);
else return ch[i];
}
}
}
}
/**
* @private
*/
apf.runNonIe = function (){
DocumentFragment.prototype.getElementById = function(id) {
return this.childNodes.length ? this.childNodes[0].ownerDocument.getElementById(id) : null;
};
// *** XML Serialization *** //
if (XMLDocument.prototype.__defineGetter__) {
//XMLDocument.xml
XMLDocument.prototype.__defineGetter__("xml", function(){
return (new XMLSerializer()).serializeToString(this);
});
XMLDocument.prototype.__defineSetter__("xml", function(){
throw new Error(apf.formatErrorString(1042, null, "XML serializer", "Invalid assignment on read-only property 'xml'."));
});
//Node.xml
Node.prototype.__defineGetter__("xml", function(){
if (this.nodeType == 3 || this.nodeType == 4 || this.nodeType == 2)
return this.nodeValue;
return (new XMLSerializer()).serializeToString(this);
});
//Node.xml
Element.prototype.__defineGetter__("xml", function(){
return (new XMLSerializer()).serializeToString(this);
});
}
/* ******** HTML Interfaces **************************************************
insertAdjacentHTML(), insertAdjacentText() and insertAdjacentElement()
****************************************************************************/
if (typeof HTMLElement!="undefined") {
if (!HTMLElement.prototype.insertAdjacentElement) {
Text.prototype.insertAdjacentElement =
HTMLElement.prototype.insertAdjacentElement = function(where,parsedNode) {
switch (where.toLowerCase()) {
case "beforebegin":
this.parentNode.insertBefore(parsedNode,this);
break;
case "afterbegin":
this.insertBefore(parsedNode,this.firstChild);
break;
case "beforeend":
this.appendChild(parsedNode);
break;
case "afterend":
if (this.nextSibling)
this.parentNode.insertBefore(parsedNode,this.nextSibling);
else
this.parentNode.appendChild(parsedNode);
break;
}
};
}
if (!HTMLElement.prototype.insertAdjacentHTML) {
Text.prototype.insertAdjacentHTML =
HTMLElement.prototype.insertAdjacentHTML = function(where,htmlStr) {
var r = this.ownerDocument.createRange();
r.setStartBefore(apf.isWebkit
? document.body
: (self.document ? document.body : this));
var parsedHTML = r.createContextualFragment(htmlStr);
this.insertAdjacentElement(where, parsedHTML);
};
}
if (!HTMLBodyElement.prototype.insertAdjacentHTML) //apf.isWebkit)
HTMLBodyElement.prototype.insertAdjacentHTML = HTMLElement.prototype.insertAdjacentHTML;
if (!HTMLElement.prototype.insertAdjacentText) {
Text.prototype.insertAdjacentText =
HTMLElement.prototype.insertAdjacentText = function(where,txtStr) {
var parsedText = document.createTextNode(txtStr);
this.insertAdjacentElement(where,parsedText);
};
}
//HTMLElement.removeNode
HTMLElement.prototype.removeNode = function(){
if (!this.parentNode) return;
this.parentNode.removeChild(this);
};
//Currently only supported by Gecko
if (HTMLElement.prototype.__defineSetter__) {
//HTMLElement.innerText
HTMLElement.prototype.__defineSetter__("innerText", function(sText) {
var s = "" + sText;
this.innerHTML = s.replace(/\&/g, "&")
.replace(//g, ">");
});
HTMLElement.prototype.__defineGetter__("innerText", function(){
return this.innerHTML.replace(/<[^>]+>/g,"")
.replace(/\s\s+/g, " ").replace(/^\s+|\s+$/g, " ");
});
HTMLElement.prototype.__defineGetter__("outerHTML", function(){
return (new XMLSerializer()).serializeToString(this);
});
}
}
/* ******** XML Compatibility ************************************************
Giving the Mozilla XML Parser the same interface as IE's Parser
****************************************************************************/
var ASYNCNOTSUPPORTED = false;
//Test if Async is supported
try {
XMLDocument.prototype.async = true;
ASYNCNOTSUPPORTED = true;
} catch (e) {/*trap*/}
//Document.prototype.onreadystatechange = null;
Document.prototype.parseError = 0;
defineProp(Array.prototype, "item", function(i){return this[i];});
defineProp(Array.prototype, "expr", "");
/*try{
XMLDocument.prototype.readyState = 0;
}catch(e){}*/
XMLDocument.prototype.$clearDOM = function(){
while (this.hasChildNodes())
this.removeChild(this.firstChild);
};
XMLDocument.prototype.$copyDOM = function(oDoc) {
this.$clearDOM();
if (oDoc.nodeType == 9 || oDoc.nodeType == 11) {
var oNodes = oDoc.childNodes;
for (var i = 0; i < oNodes.length; i++)
this.appendChild(this.importNode(oNodes[i], true));
}
else if (oDoc.nodeType == 1)
this.appendChild(this.importNode(oDoc, true));
};
//XMLDocument.loadXML();
XMLDocument.prototype.loadXML = function(strXML) {
apf.xmldb.setReadyState(this, 1);
var sOldXML = this.xml || this.serialize();
var oDoc = (new DOMParser()).parseFromString(strXML, "text/xml");
apf.xmldb.setReadyState(this, 2);
this.$copyDOM(oDoc);
apf.xmldb.setReadyState(this, 3);
apf.xmldb.loadHandler(this);
return sOldXML;
};
Node.prototype.getElementById = function(id) {};
HTMLElement.prototype.replaceNode =
Element.prototype.replaceNode = function(xmlNode) {
if (!this.parentNode) return;
this.parentNode.insertBefore(xmlNode, this);
this.parentNode.removeChild(this);
};
//XMLDocument.load
XMLDocument.prototype.$load = XMLDocument.prototype.load;
XMLDocument.prototype.load = function(sURI) {
var oDoc = document.implementation.createDocument("", "", null);
oDoc.$copyDOM(this);
this.parseError = 0;
apf.xmldb.setReadyState(this, 1);
try {
if (this.async == false && ASYNCNOTSUPPORTED) {
var tmp = new XMLHttpRequest();
tmp.open("GET", sURI, false);
tmp.overrideMimeType("text/xml");
tmp.send(null);
apf.xmldb.setReadyState(this, 2);
this.$copyDOM(tmp.responseXML);
apf.xmldb.setReadyState(this, 3);
} else
this.$load(sURI);
}
catch (objException) {
this.parseError = -1;
}
finally {
apf.xmldb.loadHandler(this);
}
return oDoc;
};
/**
* This method retrieves the current value of a property on a HTML element
* @param {HTMLElement} el the element to read the property from
* @param {String} prop the property to read
* @returns {String}
*/
var getStyle = apf.getStyle = function(el, prop) {
try{
return (window.getComputedStyle(el, "") || {})[prop] || "";
}catch(e) {}
};
//XMLDocument.setProperty
HTMLDocument.prototype.setProperty =
XMLDocument.prototype.setProperty = function(x,y) {};
/* ******** XML Compatibility ************************************************
Extensions to the xmldb
****************************************************************************/
apf.getHttpReq = function(){
if (apf.availHTTP.length)
return apf.availHTTP.pop();
return new XMLHttpRequest();
};
apf.getXmlDom = function(message, noError, preserveWhiteSpaces) {
var xmlParser;
if (message) {
if (preserveWhiteSpaces === false)
message = message.replace(/>[\s\n\r]*<");
xmlParser = new DOMParser();
xmlParser = xmlParser.parseFromString(message, "text/xml");
if (!noError)
this.xmlParseError(xmlParser);
}
else {
xmlParser = document.implementation.createDocument("", "", null);
}
return xmlParser;
};
apf.xmlParseError = function(xml) {
//if (xml.documentElement.tagName == "parsererror") {
if (xml.getElementsByTagName("parsererror").length) {
var nodeValue = xml.documentElement.firstChild.nodeValue;
if (nodeValue != null) {
var str = nodeValue.split("\n"),
linenr = str[2].match(/\w+ (\d+)/)[1],
message = str[0].replace(/\w+ \w+ \w+: (.*)/, "$1");
} else {
if (nodeValue = xml.documentElement.firstChild.getElementsByTagName('div')[0].firstChild.nodeValue) {
var linenr = nodeValue.match(/line\s(\d*)/)[1] || "N/A",
message = nodeValue.match(/column\s\d*:(.*)/)[1] || "N/A";
}
else {
var linenr = "N/A",
message = "N/A";
}
}
var srcText = xml.documentElement.lastChild.firstChild,//.split("\n")[0];
srcMsg = "";
if (srcText && srcText.nodeValue) {
srcMsg = "\nSource Text : " + srcText.nodeValue.replace(/\t/gi, " ")
}
throw new Error(apf.formatErrorString(1050, null,
"XML Parse Error on line " + linenr, message + srcMsg));
}
return xml;
};
apf.xmldb.setReadyState = function(oDoc, iReadyState) {
oDoc.readyState = iReadyState;
if (oDoc.onreadystatechange != null && typeof oDoc.onreadystatechange == "function")
oDoc.onreadystatechange();
};
apf.xmldb.loadHandler = function(oDoc) {
if (!oDoc.documentElement || oDoc.documentElement.tagName == "parsererror")
oDoc.parseError = -1;
apf.xmldb.setReadyState(oDoc, 4);
};
//
//Fix XML Data-Island Support Problem with Form Tag
apf.Init.add(function(){
var i, nodes = document.getElementsByTagName("form");
for (i = 0; i < nodes.length; i++)
nodes[i].removeNode();
nodes = document.getElementsByTagName("xml");
for (i = 0; i < nodes.length; i++)
nodes[i].removeNode();
nodes = null;
});
/*window.onerror = function(message, filename, linenr) {
if (++ERROR_COUNT > MAXMSG) return;
filename = filename ? filename.match(/\/([^\/]*)$/)[1] : "[Mozilla Library]";
new Error("---- APF Error ----\nProcess : Javascript code in '" + filename + "'\nLine : " + linenr + "\nMessage : " + message);
return false;
}*/
if (document.body)
document.body.focus = function(){};
apf.getOpacity = function(oHtml) {
return apf.getStyle(oHtml, "opacity");
};
apf.setOpacity = function(oHtml, value) {
oHtml.style.opacity = value;
};
};
/**
* Compatibility layer for Webkit based browsers.
* @private
*/
apf.runWebkit = function(){
if (XMLHttpRequest.prototype.sendAsBinary === undefined) {
if (window.ArrayBuffer) {
/**
* Binary support for Chrome 7+ which implements [ECMA-262] typed arrays
*
* For more information, see .
*/
XMLHttpRequest.prototype.sendAsBinary = function(string) {
var bytes = Array.prototype.map.call(string, function(c) {
return c.charCodeAt(0) & 0xff;
});
this.send(new Uint8Array(bytes).buffer);
};
}
}
HTMLDocument.prototype.selectNodes = XMLDocument.prototype.selectNodes = function(sExpr, contextNode) {
if (sExpr.substr(0,2) == "//")
sExpr = "." + sExpr;
try {
var oResult = this.evaluate(sExpr, (contextNode || this),
this.createNSResolver(this.documentElement),
7, null);//XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
}
catch (ex) {
try {
var oResult = this.evaluate("child::" + sExpr, (contextNode || this),
this.createNSResolver(this.documentElement),
7, null);//XPathResult.ORDERED_NODE_SNAPSHOT_TYPE
}
catch (ex) {
throw new Error("XPath error: " + ex.message + "\nLine: " + ex.lineNumber + "\nExpression: '" + sExpr + "'");
}
}
var nodeList = new Array(oResult.snapshotLength);
nodeList.expr = sExpr;
for (var i = nodeList.length - 1; i >= 0; i--)
nodeList[i] = oResult.snapshotItem(i);
return nodeList;
};
//Element.selectNodes
Text.prototype.selectNodes =
Attr.prototype.selectNodes =
Element.prototype.selectNodes = function(sExpr) {
return this.ownerDocument.selectNodes(sExpr, this);
};
//XMLDocument.selectSingleNode
HTMLDocument.prototype.selectSingleNode = XMLDocument.prototype.selectSingleNode = function(sExpr, contextNode) {
var nodeList = this.selectNodes("(" + sExpr + ")[1]", contextNode ? contextNode : null);
return nodeList.length > 0 ? nodeList[0] : null;
};
//Element.selectSingleNode
Text.prototype.selectSingleNode =
Attr.prototype.selectSingleNode =
Element.prototype.selectSingleNode = function(sExpr) {
return this.ownerDocument.selectSingleNode(sExpr, this);
};
var serializer = new XMLSerializer();
apf.insertHtmlNodes = function(nodeList, htmlNode, beforeNode, s) {
var node, frag, a, i, l;
if (nodeList) {
frag = document.createDocumentFragment();
a = [], i = 0, l = nodeList.length;
for (; i < l; i++) {
if (!(node = nodeList[i])) continue;
frag.appendChild(node);
}
}
(beforeNode || htmlNode).insertAdjacentHTML(beforeNode
? "beforebegin"
: "beforeend", s || apf.html_entity_decode(serializer.serializeToString(frag))
.replace(/<([^>]+)\/>/g, "<$1>$1>"));
};
apf.insertHtmlNode = function(xmlNode, htmlNode, beforeNode, s) {
if (htmlNode.nodeType != 11 && !htmlNode.style)
return htmlNode.appendChild(xmlNode);
if (!s) {
s = apf.html_entity_decode(xmlNode.serialize
? xmlNode.serialize(true)
: ((xmlNode.nodeType == 3 || xmlNode.nodeType == 4 || xmlNode.nodeType == 2)
? xmlNode.nodeValue
: serializer.serializeToString(xmlNode)));
}
(beforeNode || htmlNode).insertAdjacentHTML(beforeNode
? "beforebegin"
: "beforeend", s.match(/<(IMG|LINK|META|BR|HR|BASEFONT)[^\/>]*/i) ? s.replace(/<([^>]+)\/>/g, "<$1 />") : s.replace(/<([^>]+)\/>/g, "<$1>$1>"));
return beforeNode ? beforeNode.previousSibling : htmlNode.lastChild;
};
apf.getHtmlLeft = function(oHtml) {
return oHtml.offsetLeft;
};
apf.getHtmlRight = function(oHtml) {
var p;
return (((p = oHtml.offsetParent).tagName == "BODY"
? apf.getWindowWidth()
: p.offsetWidth)
- oHtml.offsetLeft - oHtml.offsetWidth
- (parseInt(apf.getStyle(p, "borderLeftWidth")) || 0)
- (parseInt(apf.getStyle(p, "borderRightWidth")) || 0));
};
apf.getHtmlTop = function(oHtml) {
return oHtml.offsetTop
};
apf.getHtmlBottom = function(oHtml) {
var p;
return (((p = oHtml.offsetParent).tagName == "BODY"
? apf.getWindowHeight()
: p.offsetHeight)
- oHtml.offsetTop - oHtml.offsetHeight
- (parseInt(apf.getStyle(p, "borderTopWidth")) || 0)
- (parseInt(apf.getStyle(p, "borderBottomWidth")) || 0));
};
apf.getBorderOffset = function(oHtml) {
return [0,0];
};
if (apf.runNonIe)
apf.runNonIe();
};
/*
* Crypt.Barrett, a class for performing Barrett modular reduction computations in
* JavaScript.
*
* Requires BigInt.js.
*
* Copyright 2004-2005 David Shapiro.
*
* You may use, re-use, abuse, copy, and modify this code to your liking, but
* please keep this header.
*
* Thanks!
*
* @author Dave Shapiro
*/
apf.crypto.Base64 = (function() {
var b64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
// public method for encoding
function encode(data) {
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, enc = "",
tmp_arr = [];
if (!data)
return data;
data = apf.crypto.UTF8.encode(data + "");
do { // pack three octets into four hexets
o1 = data.charCodeAt(i++);
o2 = data.charCodeAt(i++);
o3 = data.charCodeAt(i++);
bits = o1 << 16 | o2 << 8 | o3;
h1 = bits >> 18 & 0x3f;
h2 = bits >> 12 & 0x3f;
h3 = bits >> 6 & 0x3f;
h4 = bits & 0x3f;
// use hexets to index into b64, and append result to encoded string
tmp_arr[ac++] = b64.charAt(h1) + b64.charAt(h2) + b64.charAt(h3)
+ b64.charAt(h4);
}
while (i < data.length);
enc = tmp_arr.join("");
switch (data.length % 3) {
case 1:
enc = enc.slice(0, -2) + '==';
break;
case 2:
enc = enc.slice(0, -1) + '=';
break;
}
return enc;
}
// public method for decoding
function decode(data) {
var o1, o2, o3, h1, h2, h3, h4, bits, i = 0, ac = 0, tmp_arr = [];
if (!data) {
return data;
}
data += "";
do { // unpack four hexets into three octets using index points in b64
h1 = b64.indexOf(data.charAt(i++));
h2 = b64.indexOf(data.charAt(i++));
h3 = b64.indexOf(data.charAt(i++));
h4 = b64.indexOf(data.charAt(i++));
bits = h1 << 18 | h2 << 12 | h3 << 6 | h4;
o1 = bits>>16 & 0xff;
o2 = bits>>8 & 0xff;
o3 = bits & 0xff;
if (h3 == 64)
tmp_arr[ac++] = String.fromCharCode(o1);
else if (h4 == 64)
tmp_arr[ac++] = String.fromCharCode(o1, o2);
else
tmp_arr[ac++] = String.fromCharCode(o1, o2, o3);
}
while (i < data.length);
return apf.crypto.UTF8.decode(tmp_arr.join(""));
}
return {
decode: decode,
encode: encode
};
})();
apf.crypto.UTF8 = {
// private method for UTF-8 encoding
encode: function (string) {
// Encodes an ISO-8859-1 string to UTF-8
//
// version: 905.1217
// discuss at: http://phpjs.org/functions/utf8_encode
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: sowberry
// + tweaked by: Jack
// + bugfixed by: Onno Marsman
// + improved by: Yves Sucaet
// + bugfixed by: Onno Marsman
// * example 1: utf8_encode('Kevin van Zonneveld');
// * returns 1: 'Kevin van Zonneveld'
string = (string + "").replace(/\r\n/g, "\n").replace(/\r/g, "\n");
var tmp_arr = [],
start = 0,
end = 0,
c1, enc;
for (var n = 0, l = string.length; n < l; n++) {
c1 = string.charCodeAt(n);
enc = null;
if (c1 < 128) {
end++;
}
else if ((c1 > 127) && (c1 < 2048)) {
enc = String.fromCharCode((c1 >> 6) | 192)
+ String.fromCharCode((c1 & 63) | 128);
}
else {
enc = String.fromCharCode((c1 >> 12) | 224)
+ String.fromCharCode(((c1 >> 6) & 63) | 128)
+ String.fromCharCode((c1 & 63) | 128);
}
if (enc !== null) {
if (end > start)
tmp_arr.push(string.substring(start, end));
tmp_arr.push(enc);
start = end = n + 1;
}
}
if (end > start)
tmp_arr.push(string.substring(start, string.length));
return tmp_arr.join("");
},
// private method for UTF-8 decoding
decode: function (str_data) {
// Converts a UTF-8 encoded string to ISO-8859-1
//
// version: 905.3122
// discuss at: http://phpjs.org/functions/utf8_decode
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + input by: Aman Gupta
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// + improved by: Norman "zEh" Fuchs
// + bugfixed by: hitwork
// + bugfixed by: Onno Marsman
// + input by: Brett Zamir (http://brett-zamir.me)
// + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// * example 1: utf8_decode('Kevin van Zonneveld');
// * returns 1: 'Kevin van Zonneveld'
var tmp_arr = [], i = 0, ac = 0, c1 = 0, c2 = 0, c3 = 0;
str_data += "";
while (i < str_data.length) {
c1 = str_data.charCodeAt(i);
if (c1 < 128) {
tmp_arr[ac++] = String.fromCharCode(c1);
i++;
}
else if ((c1 > 191) && (c1 < 224)) {
c2 = str_data.charCodeAt(i+1);
tmp_arr[ac++] = String.fromCharCode(((c1 & 31) << 6) | (c2 & 63));
i += 2;
}
else {
c2 = str_data.charCodeAt(i+1);
c3 = str_data.charCodeAt(i+2);
tmp_arr[ac++] = String.fromCharCode(((c1 & 15) << 12)
| ((c2 & 63) << 6) | (c3 & 63));
i += 3;
}
}
return tmp_arr.join('');
}
};
/*
* BigInt, a suite of routines for performing multiple-precision arithmetic in
* JavaScript.
*
* Copyright 1998-2005 David Shapiro.
*
* You may use, re-use, abuse,
* copy, and modify this code to your liking, but please keep this header.
* Thanks!
*
* @author Dave Shapiro
* @author Ian Bunning
*
* IMPORTANT THING: Be sure to set maxDigits according to your precision
* needs. Use the setMaxDigits() function to do this. See comments below.
*
* Tweaked by Ian Bunning
* Alterations:
* Fix bug in function biFromHex(s) to allow
* parsing of strings of length != 0 (mod 4)
*
* Changes made by Dave Shapiro as of 12/30/2004:
*
* The BigInt() constructor doesn't take a string anymore. If you want to
* create a BigInt from a string, use biFromDecimal() for base-10
* representations, biFromHex() for base-16 representations, or
* biFromString() for base-2-to-36 representations.
*
* biFromArray() has been removed. Use biCopy() instead, passing a BigInt
* instead of an array.
*
* The BigInt() constructor now only constructs a zeroed-out array.
* Alternatively, if you pass , it won't construct any array. See the
* biCopy() method for an example of this.
*
* Be sure to set maxDigits depending on your precision needs. The default
* zeroed-out array ZERO_ARRAY is constructed inside the setMaxDigits()
* function. So use this function to set the variable. DON'T JUST SET THE
* VALUE. USE THE FUNCTION.
*
* ZERO_ARRAY exists to hopefully speed up construction of BigInts(). By
* precalculating the zero array, we can just use slice(0) to make copies of
* it. Presumably this calls faster native code, as opposed to setting the
* elements one at a time. I have not done any timing tests to verify this
* claim.
* Max number = 10^16 - 2 = 9999999999999998;
* 2^53 = 9007199254740992;
*/
apf.crypto.MD5 = {
/*
* Configurable variables. You may need to tweak these to be compatible with
* the server-side, but the defaults work in most cases.
*/
hexcase: 0, /* hex output format. 0 - lowercase; 1 - uppercase */
b64pad : "", /* base-64 pad character. "=" for strict RFC compliance */
chrsz: 8, /* bits per input character. 8 - ASCII; 16 - Unicode */
/**
* These are the functions you'll usually want to call
* They take string arguments and return either hex or base-64 encoded strings
*
* Example:
* var hash = apf.crypto.MD5.hex_md5("uzza"); //fddb7463a72e6b000abf631f558cf034
*/
hex_md5: function(s) {
return this.binl2hex(this.core_md5(this.str2binl(s), s.length * this.chrsz));
},
b64_md5: function(s) {
return this.binl2b64(this.core_md5(this.str2binl(s), s.length * this.chrsz));
},
str_md5: function(s) {
return this.binl2str(this.core_md5(this.str2binl(s), s.length * this.chrsz));
},
hex_hmac_md5: function(key, data) {
return this.binl2hex(this.core_hmac_md5(key, data));
},
b64_hmac_md5: function(key, data) {
return this.binl2b64(this.core_hmac_md5(key, data));
},
str_hmac_md5: function(key, data) {
return this.binl2str(this.core_hmac_md5(key, data));
},
/**
* Calculate the MD5 of an array of little-endian words, and a bit length
*/
core_md5: function(x, len) {
/* append padding */
x[len >> 5] |= 0x80 << ((len) % 32);
x[(((len + 64) >>> 9) << 4) + 14] = len;
var a = 1732584193, b = -271733879, c = -1732584194, d = 271733878;
for (var i = 0; i < x.length; i += 16) {
var olda = a, oldb = b, oldc = c, oldd = d;
a = this.md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);
d = this.md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);
c = this.md5_ff(c, d, a, b, x[i+ 2], 17, 606105819);
b = this.md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);
a = this.md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);
d = this.md5_ff(d, a, b, c, x[i+ 5], 12, 1200080426);
c = this.md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);
b = this.md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);
a = this.md5_ff(a, b, c, d, x[i+ 8], 7 , 1770035416);
d = this.md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);
c = this.md5_ff(c, d, a, b, x[i+10], 17, -42063);
b = this.md5_ff(b, c, d, a, x[i+11], 22, -1990404162);
a = this.md5_ff(a, b, c, d, x[i+12], 7 , 1804603682);
d = this.md5_ff(d, a, b, c, x[i+13], 12, -40341101);
c = this.md5_ff(c, d, a, b, x[i+14], 17, -1502002290);
b = this.md5_ff(b, c, d, a, x[i+15], 22, 1236535329);
a = this.md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);
d = this.md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);
c = this.md5_gg(c, d, a, b, x[i+11], 14, 643717713);
b = this.md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);
a = this.md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);
d = this.md5_gg(d, a, b, c, x[i+10], 9 , 38016083);
c = this.md5_gg(c, d, a, b, x[i+15], 14, -660478335);
b = this.md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);
a = this.md5_gg(a, b, c, d, x[i+ 9], 5 , 568446438);
d = this.md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);
c = this.md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);
b = this.md5_gg(b, c, d, a, x[i+ 8], 20, 1163531501);
a = this.md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);
d = this.md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);
c = this.md5_gg(c, d, a, b, x[i+ 7], 14, 1735328473);
b = this.md5_gg(b, c, d, a, x[i+12], 20, -1926607734);
a = this.md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);
d = this.md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);
c = this.md5_hh(c, d, a, b, x[i+11], 16, 1839030562);
b = this.md5_hh(b, c, d, a, x[i+14], 23, -35309556);
a = this.md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);
d = this.md5_hh(d, a, b, c, x[i+ 4], 11, 1272893353);
c = this.md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);
b = this.md5_hh(b, c, d, a, x[i+10], 23, -1094730640);
a = this.md5_hh(a, b, c, d, x[i+13], 4 , 681279174);
d = this.md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);
c = this.md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);
b = this.md5_hh(b, c, d, a, x[i+ 6], 23, 76029189);
a = this.md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);
d = this.md5_hh(d, a, b, c, x[i+12], 11, -421815835);
c = this.md5_hh(c, d, a, b, x[i+15], 16, 530742520);
b = this.md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);
a = this.md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);
d = this.md5_ii(d, a, b, c, x[i+ 7], 10, 1126891415);
c = this.md5_ii(c, d, a, b, x[i+14], 15, -1416354905);
b = this.md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);
a = this.md5_ii(a, b, c, d, x[i+12], 6 , 1700485571);
d = this.md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);
c = this.md5_ii(c, d, a, b, x[i+10], 15, -1051523);
b = this.md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);
a = this.md5_ii(a, b, c, d, x[i+ 8], 6 , 1873313359);
d = this.md5_ii(d, a, b, c, x[i+15], 10, -30611744);
c = this.md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);
b = this.md5_ii(b, c, d, a, x[i+13], 21, 1309151649);
a = this.md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);
d = this.md5_ii(d, a, b, c, x[i+11], 10, -1120210379);
c = this.md5_ii(c, d, a, b, x[i+ 2], 15, 718787259);
b = this.md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);
a = this.safe_add(a, olda);
b = this.safe_add(b, oldb);
c = this.safe_add(c, oldc);
d = this.safe_add(d, oldd);
}
return [a, b, c, d];
},
/*
* These functions implement the four basic operations the algorithm uses.
*/
md5_cmn: function(q, a, b, x, s, t) {
return this.safe_add(this.bit_rol(this.safe_add(this.safe_add(a, q),
this.safe_add(x, t)), s),b);
},
md5_ff: function(a, b, c, d, x, s, t) {
return this.md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
},
md5_gg: function(a, b, c, d, x, s, t) {
return this.md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
},
md5_hh: function(a, b, c, d, x, s, t) {
return this.md5_cmn(b ^ c ^ d, a, b, x, s, t);
},
md5_ii: function(a, b, c, d, x, s, t) {
return this.md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
},
/**
* Calculate the HMAC-MD5, of a key and some data
*/
core_hmac_md5: function(key, data) {
var bkey = this.str2binl(key),
ipad = Array(16),
opad = Array(16);
if (bkey.length > 16)
bkey = this.core_md5(bkey, key.length * this.chrsz);
for (var i = 0; i < 16; i++) {
ipad[i] = bkey[i] ^ 0x36363636;
opad[i] = bkey[i] ^ 0x5C5C5C5C;
}
return this.core_md5(opad.concat(
this.core_md5(ipad.concat(this.str2binl(data)), 512 + data.length * this.chrsz)
), 512 + 128);
},
/**
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
* to work around bugs in some JS interpreters.
*/
safe_add: function(x, y) {
var lsw = (x & 0xFFFF) + (y & 0xFFFF),
msw = (x >> 16) + (y >> 16) + (lsw >> 16);
return (msw << 16) | (lsw & 0xFFFF);
},
/**
* Bitwise rotate a 32-bit number to the left.
*/
bit_rol: function(num, cnt) {
return (num << cnt) | (num >>> (32 - cnt));
},
/**
* Convert a string to an array of little-endian words
* If chrsz is ASCII, characters >255 have their hi-byte silently ignored.
*/
str2binl: function(str) {
var bin = [], i,
mask = (1 << this.chrsz) - 1;
for (i = 0; i < str.length * this.chrsz; i += this.chrsz)
bin[i >> 5] |= (str.charCodeAt(i / this.chrsz) & mask) << (i%32);
return bin;
},
/**
* Convert an array of little-endian words to a string
*/
binl2str: function(bin) {
var str = [], i,
mask = (1 << this.chrsz) - 1;
for (i = 0; i < bin.length * 32; i += this.chrsz)
str.push(String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask));
return str.join("");
},
/**
* Convert an array of little-endian words to a hex string.
*/
binl2hex: function(binarray) {
var hex_tab = this.hexcase ? "0123456789ABCDEF" : "0123456789abcdef",
str = [], i;
for (i = 0; i < binarray.length * 4; i++) {
str.push(hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +
hex_tab.charAt((binarray[i>>2] >> ((i%4)*8 )) & 0xF));
}
return str.join("");
},
/**
* Convert an array of little-endian words to a base-64 string
*/
binl2b64: function(binarray) {
var tab = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
str = [], i;
for (i = 0; i < binarray.length * 4; i += 3) {
var triplet = (((binarray[i >> 2] >> 8 * ( i %4)) & 0xFF) << 16)
| (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )
| ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);
for (var j = 0; j < 4; j++) {
if (i * 8 + j * 6 > binarray.length * 32)
str.push(this.b64pad);
else
str.push(tab.charAt((triplet >> 6*(3-j)) & 0x3F));
}
}
return str.join("");
}
};
/*
* RSA, a suite of routines for performing RSA public-key computations in
* JavaScript.
*
* Requires BigInt.js and Barrett.js.
*
* Copyright 1998-2005 David Shapiro.
*
* You may use, re-use, abuse, copy, and modify this code to your liking, but
* please keep this header.
*
* Thanks!
*
* @author Dave Shapiro
*/
(function(global) {
function rotate_left(n,s) {
var t4 = ( n<>>(32-s));
return t4;
};
/*
function lsb_hex(val) { // Not in use; needed?
var str="";
var i;
var vh;
var vl;
for ( i=0; i<=6; i+=2 ) {
vh = (val>>>(i*4+4))&0x0f;
vl = (val>>>(i*4))&0x0f;
str += vh.toString(16) + vl.toString(16);
}
return str;
};
*/
function cvt_hex(val) {
var str="";
var i;
var v;
for ( i=7; i>=0; i-- ) {
v = (val>>>(i*4))&0x0f;
str += v.toString(16);
}
return str;
};
global.SHA1 = function(str) {
// Calculate the sha1 hash of a string
//
// version: 905.3122
// discuss at: http://phpjs.org/functions/sha1
// + original by: Webtoolkit.info (http://www.webtoolkit.info/)
// + namespaced by: Michael White (http://getsprink.com)
// + input by: Brett Zamir (http://brett-zamir.me)
// + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net)
// - depends on: utf8_encode
// * example 1: sha1('Kevin van Zonneveld');
// * returns 1: '54916d2e62f65b3afa6e192e6a601cdbe5cb5897'
var blockstart, i, j, W = new Array(80),
H0 = 0x67452301,
H1 = 0xEFCDAB89,
H2 = 0x98BADCFE,
H3 = 0x10325476,
H4 = 0xC3D2E1F0,
A, B, C, D, E, temp;
str = apf.crypto.UTF8.encode(str);
var str_len = str.length,
word_array = [];
for (i = 0; i < str_len - 3; i += 4) {
j = str.charCodeAt(i) << 24 | str.charCodeAt(i + 1) << 16 |
str.charCodeAt(i + 2) << 8 | str.charCodeAt(i + 3);
word_array.push(j);
}
switch (str_len % 4) {
case 0:
i = 0x080000000;
break;
case 1:
i = str.charCodeAt(str_len - 1) << 24 | 0x0800000;
break;
case 2:
i = str.charCodeAt(str_len - 2) << 24 | str.charCodeAt(str_len - 1)
<< 16 | 0x08000;
break;
case 3:
i = str.charCodeAt(str_len - 3) << 24 | str.charCodeAt(str_len - 2)
<< 16 | str.charCodeAt(str_len - 1) << 8 | 0x80;
break;
}
word_array.push( i );
while ((word_array.length % 16) != 14)
word_array.push( 0 );
word_array.push(str_len >>> 29);
word_array.push((str_len << 3) & 0x0ffffffff);
for (blockstart = 0; blockstart < word_array.length; blockstart += 16) {
for (i = 0; i < 16; i++)
W[i] = word_array[blockstart + i];
for (i = 16; i <= 79; i++)
W[i] = rotate_left(W[i - 3] ^ W[i - 8] ^ W[i - 14] ^ W[i - 16], 1);
A = H0;
B = H1;
C = H2;
D = H3;
E = H4;
for (i = 0; i <= 19; i++) {
temp = (rotate_left(A, 5) + ((B & C) | (~B & D)) + E + W[i]
+ 0x5A827999) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
for (i = 20; i <= 39; i++) {
temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0x6ED9EBA1)
& 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
for (i = 40; i <= 59; i++) {
temp = (rotate_left(A, 5) + ((B & C) | (B & D) | (C & D)) + E + W[i]
+ 0x8F1BBCDC) & 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
for (i = 60; i <= 79; i++) {
temp = (rotate_left(A, 5) + (B ^ C ^ D) + E + W[i] + 0xCA62C1D6)
& 0x0ffffffff;
E = D;
D = C;
C = rotate_left(B, 30);
B = A;
A = temp;
}
H0 = (H0 + A) & 0x0ffffffff;
H1 = (H1 + B) & 0x0ffffffff;
H2 = (H2 + C) & 0x0ffffffff;
H3 = (H3 + D) & 0x0ffffffff;
H4 = (H4 + E) & 0x0ffffffff;
}
temp = cvt_hex(H0) + cvt_hex(H1) + cvt_hex(H2) + cvt_hex(H3) + cvt_hex(H4);
return temp.toLowerCase();
};
})(apf.crypto);
/**
* @constructor
* @parser
*
* @author Rik Arends
* @version %I%, %G%
* @since 3.0
*/
apf.lm = new (function(){
var statement_lut = { // all js statements to see its NOT an expression
"var": 1, "for": 1, "while": 1, "do": 1, "if": 1, "else": 1,
"switch": 1, "case": 1, "break": 1, "continue": 1, "default": 1,
"function":2, "return": 1, "try": 1, "catch": 1, "throw":1,
"debugger": 1, "alert": 1, "confirm": 1,"setTimeout": 1,"setInterval": 1,"delete": 1, "export": 1, "import": 1,
"label": 1, "foreach":1, "each": 1, "eachrev":1, "foreachrev":1, "node": 1, "local": 1, "yield": 1,
"let":1, "finally":1, "delete":1
},
type_lut = { // used for optimizing the parse regexp
"\n": 1, "\r\n": 1, "==":2, "++":2, "--":2, '"': 5, "'": 5,
"": 6, "/*": 6, "//": 6, "*/": 6, "{": 7, "}": 8,
"[": 9, "]": 10, "(": 11, ")": 12, "<": 13, ">": 14, "+=":2,
"-=":2, "/=":2, "*=":2, "!=":2
},
type_close = { // handy
"{": "}", "[": "]", "(": ")", "{{":"}"
},
xpath_axes = { // used to detect xpath axes or model
"ancestor": 1, "ancestor-or-self": 1, "attribute": 1, "child": 1,
"descendant": 1, "descendant-or-self": 1, "following": 1,
"following-sibling": 1, "namespace": 1, "parent": 1, "preceding": 1,
"self": 1
},
misc_tok = { // misc token lookup
";":1, ",":2, "^":3, "=":4, "+=":4, "-=":4, "/=":4, "*=":4, "/":5, ":":6
},
xpath_lut_code = { // which autoxpath to use when doing macro({xpath})
"~": "_val(_n,", "%": "_nod(_n,", "*": "_nods(_n,", "#": "_cnt(_n,", "$": "_lng("
},
xpath_lut_text = { // which autoxpath to use when doing xpath macros in textmode
"~": "_val(_n,", "%": "_xml(_n,", "*": "_xmls(_n,", "#": "_cnt(_n,", "$": "_lng("
},
xpath_lut_attr = { // xpath lut for node attributes
"~": "_val(_n,", "%": "_val(_n,", "*": "_val(_n,", "#": "_cnt(_n,", "$": "_lng("
},
xpath_lut_node,
xpath_lut_node_normal = { // normal xpath lookup
"~": "_val(_n,", "%": "_xml(_n,", "*": "_xmls(_n,", "#": "_cnt(_n,", "$": "_lng("
},
xpath_lut_node_langedit = { // language edit xpath lookup
"~": "_val(_n,", "%": "_xml(_n,", "*": "_xmls(_n,", "#": "_cnt(_n,", "$": "_lnged("
},
pre_regexp = {
"[":1, "(":1, ",":1, "=":1, "return":1, "throw":1
},
pre_xpath = {
"else":1, "return":1, "delete":1
},
pre_plain = {
"do":1, "else":1, "try":1
},
op_lut = { // obj.prop += operator lut
"=" : "_asn(", "+=": "_add(", "-=": "_sub(", "/=": "_div(", "*=": "_mul("
},
new_block = {
"+":1, "%":1, "-":1, "/":1, "=":1, "(":1, "?":1, "|":1, "^":1, "[":1,
"&":1, "*":1, "!":1, ":":1, "<":1, ",":1
},
out_context_word = { // token preceeding a word signalling a new output
"{":1, "} ":1, ")":1, ") ":1, ";":1, "\n":1, "else":1
},
out_context_paren = { // token preceeding a paren signalling a new output
"{":1, ";":1, "\n":1, "else":1
}, // special markers: ') ' tail of xpath macro. ') ' func def, tok=') ' its not an if while etc.
markup_in_code_lut = {
"} ":1, ") ":1,// the } used when it wasnt a code-expression
"(":1, /*")":1,*/ ";":1, "&":1, "^":1, "|":1, ",":1, '"':1, "'":1, "=":1,
"!=":2,"+=":2, "-=":2, "/=":2, "*=":2, "?":1, "{":1, "}":1, ">":1, "[":1,
/*"]":1,*/ "+":1, ":":1, "else":1, "return":1
},
block_autoappend = { // token preceeding block signalling auto append
'"':1, "'":1, ">":1, "]":1, "}":1
},
unesc_lut = { // unescape in code and xpath mode
"\\\"": "\"", "\\\'": "\'", "\\{": "{", "\\}": "}", "\\[": "[",
"\\]": "]", "\\(":"(", "\\)":")", "\\\\":"\\"
},
call_exclusion = {
"alert": 1, "confirm" :1, "setTimeout":1, "setInterval":1, "switch":1,
"call":1, "return":1, "throw":1, "case":1, "catch":1,
"abs":1,"acos":1,"asin":1,"atan":1,"atan2":1,"ceil":1,
"cos":1,"exp":1,"floor":1,"log":1,"max":1,"min":1,
"pow":1,"random":1,"round":1,"sin":1,"sqrt":1,"tan":1,"lin":1,"linear":1,
"idx":1,"sort":1,"typeof":1
},
is_out_space = {
" ":1, "\n":1
},
newline_notallowed = {
"{":1, ";":1, "(":1, "\n":1
},//@todo !verify and document! character escaping system
unesc_str = { // unescape in string mode
"\\{": "{", "\\}": "}", "\\[": "[", "\\]": "]", "\\(": "(", "\\)": ")"
},
unesc_txt = { // unescape in text mode
"\\{" : "{", "\\}" : "}", "\\[" : "[", "\\]" : "]", "\\(" : "(",
"\\)" : ")", "\\\\": "\\\\\\\\", "\\" :"\\\\", "\\<" : "<", "\\>" : ">"
},
xml_code_operators = { // word to operand lookup table for easy use in xml
"lte": "<=", "gte": ">=", "lt": "<", "gt": ">", "and": "&&", "or": "||",
"andbin": "&", "orbin": "|", "LTE": "<=", "GTE": ">=", "LT": "<",
"GT": ">", "AND": "&&", "OR": "||", "ANDBIN": "&", "ORBIN": "|"
},
xpath_macro = { // which autoxpath to use when doing macro({xpath})
0 : "_val(_n,",
1 : "_valcr(_n,_cr,",
2 : "_nod(_n,",
3 : "_nodcr(_n,_cr,",
4 : "_nods(_n,",
5 : "_xpt(_n,",
6 : "_valst(_n,",
7 : "_valed(_n,",
8 : "_valattr(_n,",
"foreach" : "_nods(_n,",
"each" : "_nods(_n,",
"foreachrev": "_nods(_n,",
"eachrev" : "_nods(_n,",
"xabs" : "_valst(_n,",
// "edit" : "_argwrap(_n,",
// "edit" : "_val(_n,", // toggled by liveedit
"local" : "_nod(_n,",
"tagName" : "_nod(_n,",
"localName" : "_nod(_n,",
"xml" : "_xmlq(",
"_call" : "_val(_n,"
},
xpath_model = { // which autoxpath to use when doing macro({xpath})
"_val(_n," : "_valm(",
"_valcr(_n,_cr,": "_valcr(0,_cr,",
"_nod(_n," : "_nodm(",
"_nodcr(_n,_cr,": "_nodcr(0,_cr,",
"_nods(_n," : "_nodsm(",
"_argwrap(_n," : "_argwrapm(",
"_xml(_n," : "_xml(0,",
"_xmls(_n," : "_xmls(0,",
"_cnt(_n," : "_cntm(",
"_xpt(_n," : "_xptm(",
"_valst(_n," : "_valm(",
"_valed(_n," : "_valed(0,",
"_lng(" : "_lng(",
"_lnged(" : "_lnged("
},
parserx = /(\r?[\n]|\/\*|\*\/|\/\/|\<\!\-\-|\-\-\>|[=\!+\/\*-]=|\+\+|\-\-|["'{(\[\])}\]\<\>]|$)|([ \t]+)|([a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF.$_][\w.$_]*)|(\d[x\d.]*)|(\\?[\w._?,:;!=+-\\\/^&|*"'[\]{}()%$#@~`<>]?)/g,
selfrx = /(^|\|)(?!\@|text\(\)|\.\.|[\w\-\:]+?\:\:)/g, // inject self regexp
macro_o = {},
macro_c = {},
macro_m = {},
// config vars
c_async_lut = apf.$asyncObjects || { // used to figure out if the thing before the. is an async obj
"comm" :1,
"rpc" :1,
"http" :1,
"apf.ajax" :1
},
c_process_async,
c_xpathmode, // guess 'node' as the type for {} o_xpathpairs, 1 = node, 2 = nodes
c_elemxpath, // which xpath macro to use inside an element
c_statexpath, // which xpath to use for the stateful value
c_injectself, // add self:: in some o_xpathpairs
c_propassign, // support property assigns
c_export, // export function to some object
// outputs
o, ol, // output and output len
o_asyncs, // number of async calls
o_xpathpairs, // all xpaths and their models in pairs
o_props, // the js properties found
o_segs, // segments at groundlevel
o_xpaths, // xpaths at groundlevel
o_models, // number of xpaths with models
// temp and state vars
s = [], sl, // scopestack and scopestack len
bt = [], // backtrack lut
bts = [], // backtrack string stack
parse_mode, // the parse parse_mode
scope, // ol of a scope begni
segment, // the ol of a segment begin
start_tok, // the token a string or comment was started with
str,str_len, // length of the input string
line_no, // line number we are at
nesting, // nesting count
// last state vars
last_tok, // last token
last_type, // last type
last_dot, // . pos when last token was a word
last_model, // last model found
last_prop, // last property found
last_cmt_mode, // the parse mode outside the comment
last_cmt_tok, // last token before comment
last_cmt_type, // last type before comment
last_line, // offset of last newline
last_ns, // last namespace found
last_word; // last word in code mode
// macros used in code()
macro_o["if"] = "if(",
macro_c["if"] = ")",
macro_o["while"] = "while(",
macro_c["while"] = ")",
macro_o["for"] = "for(",
macro_c["for"] = ")",
macro_o["switch"] = "switch(",
macro_c["switch"] = ")",
macro_o["catch"] = "catch(",
macro_c["catch"] = ")",
macro_c["function"] = ") ";
macro_o.foreach =
macro_o.each = "\nfor(var _t=_t||[],_t=(_t.push(_n,0,(",
macro_c.foreach =
macro_c.each = ")||[]),_t);(_n=_t[_t.length-1][_t[_t.length-2]++])||(_t.length-=2,_n=_t.pop(),0);)",
macro_o.foreachrev =
macro_o.eachrev = "\nfor(var _t=_t||[],_t=(_t.push(_n,0,(",
macro_c.foreachrev =
macro_c.eachrev = ")||[]),_t);(_n=_t[_t.length-1][_t[_t.length-1].length-(_t[_t.length-2]++)-1])||(_t.length-=2,_n=_t.pop(),0);)",
macro_o.local = "\nfor(var _t=_t||[],_t=(_t.push(_n,((_n=_local(",
macro_c.local = ")),1)),_t);(_t[_t.length-1]--&&_n)||(_t.length--,_n=_t.pop(),0);)",
macro_o._editlm = "_valedx(true, ", // only serves to switch default xpath in edit([xpath])
macro_o._editnormal = "_valedx(false, ", // only serves to switch default xpath in edit([xpath])
macro_c.edit = ")",
macro_o.xabs = " ( ",
macro_c.xabs = " ) ",
macro_o.localName = "_localName(_n",
macro_c.localName = ")",
macro_o.output = "_o.join(''",
macro_c.output = ")",
macro_o.reset = "(_o=[],l=0",
macro_c.reset = ")",
macro_o.index = "apf.getChildNumber.call(apf",
macro_c.index = ")",
macro_o.item = "(_t[_t.length-1][_t[_t.length-2]-1]",
macro_c.item = ")",
macro_o.first = "(_t[_t.length-2]==1",
macro_c.first = ")",
macro_o.last = "(_t[_t.length-2]==_t[_t.length-1].length",
macro_c.last = ")",
macro_o.total = "(_t[_t.length-1].length",
macro_c.total = ")",
macro_o.pos = "(_t[_t.length-2]-1",
macro_c.pos = ")",
macro_o.tagName = "_tagName(_n",
macro_c.tagName = ")",
macro_o._nodeValue = "_nodeValue(_n",
macro_c._nodeValue = ")",
macro_c.async = "])",
macro_c.precall = "])",
macro_c._call = ")";
var call_args_lut = {
_call: ".call(_n",
localName: macro_o.localName,
tagName: macro_o.tagName,
nodeValue: macro_o.nodeValue,
index: macro_o.index
},
// centralized code fragments used in parser/generator
cf_block_o = "(function(){var _o=[],_l=0;\n",
cf_block_c = ";return _l==1?_o[0]:_o.join('');}).call(this)",
cf_async_o = "_async(_n,_c,_a,_w,_f,this,",
cf_async_m = "',_a[++_a.i]||[",
cf_obj_output = "_r=",
cf_mode_output,
cf_str_output = "_o[_l++]=",
cf_def_output = "",
cf_func_o = "{var _o=[],_l=0,_n=this;\n",
cf_func_c = ";\nreturn _l==1?_o[0]:_o.join('');}",
// compile chunks used in compile/match
cc_async_o = "(_a=_a||{}).i=0;try{\n",
cc_async_c = "}catch(_e){if(_e.x)return;throw(_e);}\n",
//cc_async_o = "(_a=_a||{}).i=0;",
//cc_async_c = "",
cc_pc_o = "(_a=_a||{}).i=0;try{_precall(_w);",
cc_pc_c = "}catch(_e){if(_e.x)return;throw(_e);}",
cc_opt_o = "with(_w){",
cc_opt_c = "}",
cc_v_blk_o = "var _o=[],_l=0;_o[_l++]=",
cc_v_blk_ob = "var _o=[],_l=0;\n",
cc_v_blk_c = ";\nreturn _ret(_l==1?_o[0]:_o.join(''));",
cc_v_blk_cb = ";\n_c(_ret(_l==1?_o[0]:_o.join('')),apf.SUCCESS,apf.$lmx);apf.$lmx=null;",
cc_v_ret_o = "return _ret(",
cc_v_ret_c = ");",
cc_v_cb_o = "_c(_ret(",
cc_v_cb_c = "),apf.SUCCESS,apf.$lmx);apf.$lmx=null;\n",
cc_o_blk_o = "var _r=",
cc_o_blk_ob = "var _r;",
cc_o_blk_c = ";\nreturn _r;",
cc_o_blk_cb = ";\n_c(_r,apf.SUCCESS,apf.$lmx);apf.$lmx=null;",
cc_o_blk_ce = ";\n_c(0,apf.SUCCESS,apf.$lmx);apf.$lmx=null;;",
cc_o_ret_o = "return ",
cc_o_ret_c = "",
cc_o_cb_o = "_c(",
cc_o_cb_c = ",apf.SUCCESS);",
cc_f_async_o = "var _f=function(_n,_c,_w,_a){",
cc_f_opt_o = "var _f=function(_n,_w){",
cc_f_o = "var _f=function(_n){",
cc_fc_async_o = "var _f=function(_n,_c,_w,_cr,_a){",
cc_fc_opt_o = "var _f=function(_n,_w,_cr,){",
cc_fc_o = "var _f=function(_n,_cr){",
cc_fe_async_o = "var _f=function(event,_c,_w,_a,_n){",
cc_fe_opt_o = "var _f=function(event,_w,_n){",
cc_fe_o = "var _f=function(event,_n){",
cc_f_c = "}",
cc_f_match_o = "var _f=function(_m){",
cc_m_m_blk = ";\nif(_n=_r){if(!_n.nodeType)_n=_m;",
cc_m_m_value_o = ";\nif(_n=",
cc_m_m_value_c = "){if(!_n.nodeType)_n=_m;",
cc_m_v_string = "\nreturn ",
cc_m_v_o = "\nreturn _ret(",
cc_m_v_c = ");",
cc_m_n_string = "\nreturn _n;",
cc_m_n_o = "\nreturn (_r = (",
// decision point for compileMatch node-mode for the return type
cc_m_n_c = "))?(_r.nodeType?_r:_n):(_r===null?null:_n);",
cc_m_o = "var _r, _n = _m;",
cc_m_brk = ";\n_n = _m;",
cc_m_v_ret = "\nreturn _ret(_nodeValue(_n));" ,
cc_m_n_ret = "\nreturn _n;" ,
cc_m_c = "\n}";
function switchToBlock(no_output){ // used to switch expression mode to block mode
var u, v;
if (o[scope-1] == "{{")
u = scope-1; // scan for our root expression block to switch to block
else
for (v = sl - 2, u = 0; v >= 0 && o[u=(s[v] & 0xfffffff) - 1] != "{{"; v -=2 ){};
if (!no_output && ol > u + 1) // inject auto output unless no output or nothing to output in buffer
o[u] = cf_block_o + cf_str_output
else
o[u] = cf_block_o;
parse_mode = 1;
}
function parser(tok, rx_lut, rx_white, rx_word, rx_num, rx_misc, pos) {
var u, v, w,
type = rx_lut ? type_lut[rx_lut] : (rx_white ? 0 : (rx_word ? 3 : (rx_num ? 4 : (tok ? 2 : 15))));
switch (parse_mode) {
case 0: // ===================== expression parse_mode =========================
case 1: // ========================== block parse_mode =========================
switch (type) {
case 0: // -------- whitespace --------
if ((last_type == 3 && last_tok!='$') || last_type == 4)
o[ol++] = " ";
else if (xpath_lut_code[last_tok])
last_type = 0;// make last_type visible to xpathmode select
break;
case 1: // -------- newline --------
line_no++,
last_line = pos;
if (o[ol-1] != "\n" && !newline_notallowed[last_tok])
o[ol++] = "\n";
if (xpath_lut_code[last_tok])
last_type = 0;// make last_type visible to xpathmode select
break;
case 2: // -------- misc --------
if (v = misc_tok[tok]) {
switch (v) {
case 1: // ';'
if (!s[sl-1]) {// close = macro
o[ol++] = ")",
sl -= 2;
}
if (!parse_mode) { // dont do ; inject newline instead
if (o[ol-1] != "\n" && last_tok != "{" && last_tok != ";")
o[ol++] = "\n";
}
else if (!sl || s[sl - 1]) // dont inject ; if we are in nested assignment macros
o[ol++] = ";";
break;
case 2: // ','
if (!s[sl - 1]) { // close = macro
o[ol++] = ")",
sl -= 2;
}
o[ol++] = ",";
break;
case 3: //'^' // dont output
if (o[ol-1] == "\n" || o[ol - 1] == ";" || last_tok=="{"
|| last_tok == "} " || ol == scope) { // dont output-shortcut requirements
if (!parse_mode)
switchToBlock();
o[ol++] = " "; // two spaces make it modify the output-ifs outcome
}
else
o[ol++] = "^";
break;
case 4: //'= += -= assignment macro mode
if (last_tok!='<' && last_tok!='>'){
// we should only switch to block when we are not in a ( ) scope
if (!parse_mode && o[scope-1]!='(')
switchToBlock(true);
o[ol++] = tok;
// lets scan in reverse to see if we have an output or a non-output
for (v = ol; v >= scope && !statement_lut[o[v]] && !((o[v] == " "
|| o[v] == (nesting ? cf_str_output : cf_mode_output)) && (o[v]="",1)); v--){};
if (last_type == 3 && last_dot>0 && last_tok.charAt(0)!="."){ // prop = macro
if (c_propassign) {
ol -= 2;
while (is_out_space[o[ol]])
ol--;
w = last_tok;
o[ol++] = op_lut[tok], o[ol++] = w.slice(0,last_dot),
o[ol++] = ",'", o[ol++] = w.slice(last_dot+1),
o[ol++] = "',", s[sl++] = scope | (parse_mode << 28),
s[sl++] = ""; // notabene, this stored item is checked everywhere
}
}
}else{
o[ol++] = tok;
}break;
case 5: // '/' // regexp mode
if (pre_regexp[last_tok]) {
s[sl++] = scope | (parse_mode << 28);
s[sl++] = o[ol++] = tok;
scope = segment = ol - 1;
nesting++, parse_mode = 5, start_tok = tok;
}
else
o[ol++] = "/";
break;
case 6: // ':' // switch to {x:1} object mode
if (sl > 2 && s[sl - 1] == "{{" && (ol < scope + 4 && last_type == 5)
|| (ol < scope + 3 && (last_type == 3 || last_type == 4))) {
o[scope-1] = s[sl-1] = "{"
parse_mode = (v = s[sl - 2]) >> 28;
s[sl-2] = v & 0xfffffff,
nesting--;
}
else if (o[ol - 3] == "case" || (last_type == 5 && last_word == "case"))
tok = ";"; //fixes auto output problem
o[ol++] = ":";
break;
default:
o[ol++] = tok;
break;
}
}
else
o[ol++] = unesc_lut[tok] || tok;
break;
case 3: // -------- word --------
case 4: // ------- number -------
if ( v = xml_code_operators[tok] ){
o[ol++] = tok = v, type = 2;
} else {
v = u = w = 0;// last_word used for case 'bla bla':
last_dot = (last_word = tok).lastIndexOf(".");
if (tok.charAt(0) != '.' // .obj shouldnt trigger block
&& ((v = (u = ((out_context_word[last_tok] // check if we need to switch
|| o[ol - 1] == "\n") && !new_block[last_tok]))
&& !s[sl - 1].indexOf("{") && ol > scope)
|| (w = statement_lut[tok])) && !parse_mode){ // check statement
if (w == 2 && s[sl - 1].indexOf("{")) w = 0; // (function() shouldnt trigger blockmode
switchToBlock(w); // pass in statement_lut[tok] as outputflag
}
if (u && !s[sl - 1]) { // assign macro close
o[ol-1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
if (v && parse_mode && !statement_lut[tok] && !call_exclusion[tok]) // inject output
o[ol++] = (nesting ? cf_str_output : cf_mode_output);
if (last_dot > 0 && tok.charAt(0) != ".") // store property
o_props[o[ol++] = last_prop = tok] = 1;
else o[ol++] = tok;
}
break;
case 5: // -------- stringquotes --------
if ((v = (u = ((out_context_word[last_tok] || o[ol - 1]== "\n" )
&& !new_block[last_tok])) && !s[sl - 1].indexOf("{")
&& ol > scope) && !parse_mode) // check if we need to switch to block mode
switchToBlock();
if (u && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
if (v) { // generate output
o[ol++] = (o[ol-2] != "\n" && block_autoappend[last_tok])
? "+"
: (nesting ? cf_str_output : cf_mode_output);
}
else if (block_autoappend[last_tok])
o[ol++] = "+";
s[sl++] = scope | (parse_mode << 28), s[sl++] = o[ol++] = tok;
scope = segment = ol - 1, nesting++, parse_mode = 5, start_tok = tok;
break;
case 6: // -------- comment --------
if (tok == "*/" || tok== "-->")
throw {
t: "Unmatched comment "+tok,
p: pos
};
last_cmt_mode = parse_mode, last_cmt_tok = last_tok,
last_cmt_type = last_type, parse_mode = 6, start_tok = tok;
break;
case 7: // -------- { --------
if (o[ol - 1] == ") " || (o[ol - 2] == ") " && ol--)) { // ') ' is function def
if (s[sl - 1] != "(" && s[sl - 1] != "[") {
s[sl++] = scope | (parse_mode << 28),
s[sl++] = "{{", o[ol++] = cf_func_o,
scope = ol, parse_mode = 1, nesting++, o[ol++] = ""; // make the scope check pass
}
else {
s[sl++] = scope, s[sl++] = o[ol++] = tok, scope = ol;
parse_mode = 1;
}// for do else..etc below
}
else if ((macro_o[s[sl + 1]] && last_tok == ") ") || pre_plain[last_tok]) {
s[sl++] = scope, s[sl++] = o[ol++] = tok, scope = ol;
o[ol++] = "";
}
else {
if ((v = (u = ((out_context_word[last_tok]||o[ol - 1] == "\n")
&& !new_block[last_tok]))
&& !s[sl - 1].indexOf("{") && ol > scope) && !parse_mode)
switchToBlock(); // block mode detection
if (u && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""),
o[ol++] = ")", o[ol++] = "\n", v = 1, sl -= 2;
}
if (v) { // inject output, +''+ is when between two { } { } (supposedly)
o[ol++] = (o[ol - 2] != "\n" && block_autoappend[last_tok])
? "+''+"
: (nesting ? cf_str_output : cf_mode_output);
}
else if (block_autoappend[last_tok]) // inject append
o[ol++] = (last_tok == "}") ? "+''+" : "+";
s[sl++] = scope | (parse_mode << 28), s[sl++] = o[ol++] = "{{";
if (!nesting && scope != ol) // count output segments on nesting 0
o_segs++;
nesting++, segment = scope = ol, parse_mode = 0;
}
break;
case 8: // -------- } --------
if (!s[sl - 1]) // close = macro
o[ol++] = ")", o[ol++] = "\n",sl -= 2;
if (type_close[v = s[--sl]] != (o[ol++] = tok))
throw {
t: "Cannot close " + v + " with " + tok,
p: pos
};
if (v == "{{") { // closing code block
if (scope == ol - 1) {
if ( (s[sl - 1] >> 28) <= 1) // empty code in code
o[scope-1] = "{", o[ol - 1] = "}";
else // empty code elsewhere
o[scope - 1] = o[ol - 1] = "'";
}
else {
if (!parse_mode) { // expression wraps in ()
o[scope - 1] = "(",
o[ol - 1] = ")";
}
else { // codeblock wraps in (function(){})()
if (o[scope - 1] == cf_func_o) {
if (scope == ol - 2)
o[scope - 1] = "{", o[ol - 1] = "}";
else
o[ol - 1] = cf_func_c;
}
else
o[ol - 1] = cf_block_c;
}
}
parse_mode = (v=s[--sl])>>28, scope = v&0x0fffffff;
segment = ol, nesting--;
if (!nesting) // count segs on nesting level 0
o_segs++;
if (parse_mode == 7) // attribute hack
o[ol++] = "+\"\\\"", parse_mode = 4;
} else scope = s[--sl]; // was object def or if (){}
break;
case 9: // -------- [ --------
if (((last_type == 3 && !pre_xpath[last_tok] && last_tok!='$') || last_tok == ")" || last_tok == "]") && o[ol - 1] != "\n") {
o[ol++] = "[", s[sl++] = scope | (parse_mode << 28), //was array index
s[sl++] = tok, segment = scope = ol;
}
else {
last_model = null;
if ((w = xpath_lut_code[last_tok])) {
ol--, last_tok = o[ol-1]; // xpath with *%$#
}
else {
w = xpath_macro[s[sl - 1]] || xpath_macro[nesting ? 0 : c_xpathmode];
}
if ((v = (u = ((out_context_word[last_tok] || o[ol - 1] == "\n")
&& !new_block[last_tok])) && !s[sl - 1].indexOf("{")
&& (ol > scope || s[sl - 1].length == 1)) && !parse_mode)
switchToBlock(); // check if we need to switch to block mode
if (u && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
if (v) { // inject output
o[ol++] = (o[ol - 2] != "\n" && block_autoappend[last_tok])
? "+"
: (nesting ? cf_str_output : cf_mode_output);
}
else if (block_autoappend[last_tok]) // inject append
o[ol++] = "+";
if (!nesting && ol!=scope)
o_segs++;
// store scope in bt for reparse of array
nesting++, s[sl++] = scope|(parse_mode<<28), s[sl++] = o[ol++] = w,
segment = scope = ol, bt[scope] = pos, parse_mode = 3;
}
break;
case 10: // -------- ] --------
if (!s[sl-1]) // close = macro
o[ol++]=")",sl -=2;
if ( type_close[v = s[--sl]] != (o[ol++] = tok))
throw {
t: "Cannot close " + v + " with " + tok,
p: pos
};
scope = s[--sl]&0xfffffff; // clean scope of possible parse_mode 1
break;
case 11: // -------- ( --------
if ( ((v = (u=((out_context_paren[last_tok]||o[ol-1]=="\n") &&
!new_block[last_tok])) && !s[sl-1].indexOf("{") &&
ol>scope)) && !parse_mode)
switchToBlock();
if (u && !s[sl-1]) // close = macro
o[ol-1]=="\n"&&(o[ol-1]=""),o[ol++]=")", o[ol++]="\n",v = 1,sl -=2;
if (v && parse_mode) // inject output
o[ol++] = (nesting?cf_str_output:cf_mode_output), last_type = 0;
if (w = macro_o[last_tok]) {
if (o[ol-1]==" ") ol--; // support func ()
o[ol-1] = w, s[sl++] = scope, s[sl++] = last_tok, scope = segment = ol;
}
else {
if (last_type == 3) { // word(
if (last_dot < 0) { // no dot
v = 0;
if (last_tok == "function" || o[ol - 3] == "function" || o[ol - 4] == "function") {
s[sl++] = scope, s[sl++] = "function", //func def
o[ol++] = "(", scope = segment = ol;
//TODO! check the depth with which functions are made global
if (last_tok!="function" && c_export && sl==4) {
o[v=(o[ol - 4] == "function")?(ol-4):(ol-5)] =
"var "+last_tok+" = "+c_export+"."+last_tok+" = function";
o[v+2] = "";
}
}
else { // its a call and not a new
if (!call_exclusion[last_tok] && o[ol-3]!="new") {
o[ol++] = ".call(_n", s[sl++] = scope,
s[sl++] = "_call", scope = segment = ol;
}
else { // its an excluded call
s[sl++] = scope, s[sl++] = o[ol++] = tok,
scope = segment = ol;
}
}
}
else {
if (last_dot > 1 && c_process_async && (c_async_lut[v = last_tok.substring(0,last_dot)] || c_async_lut[v = last_tok])) {// its an async call
if (o[--ol] == " ")
ol--;
o[ol++] = cf_async_o, o[ol++] = v, o[ol++] = ",'";
o[ol++] = last_tok.slice(last_dot + 1);
o[ol++] = cf_async_m, s[sl++] = scope, s[sl++] = "async",
scope = segment = ol, o_asyncs++;
}
else { // its a obj.prop() type call
if (last_tok.indexOf('.')!=last_dot) // obj.prop.call();
o_props[last_tok.slice(0,last_dot)] = 1;
s[sl++] = scope, s[sl++] = o[ol++] = tok,
scope = segment = ol;
}
}
}
else { // function object call
s[sl++] = scope, s[sl++] = o[ol++] = tok,
scope = segment = ol;
} // dont store calls as props
if (last_tok == last_prop)
delete o_props[last_tok];
}
break;
case 12: // -------- ) --------
if (!s[sl - 1]) { // close = macro
o[ol-1] == "\n" && (o[ol-1] = ""), o[ol++] = ")",
o[ol++]="\n", v = 1, sl -= 2;
}
if (w = macro_c[v = s[--sl]]) { // closing a macro
if (v != "_call")
tok = ") "; // make sure any [ ] doesnt get interpreted as array index
if ((u = call_args_lut[v]) && u != o[ol - 1])
o[scope - 1] = u + ",";// do , insertion for argless macros
o[ol++] = w; // write close-end of macro
}
else if (type_close[v] != (o[ol++] = tok)) {
throw {
t: "Cannot close " + v + " with " + tok,
p: pos
};
}
scope = s[--sl] & 0xfffffff; // scope should be unimpacted
break;
case 13: // -------- < --------
// check if < is markup or not
if (ol == scope || markup_in_code_lut[last_tok] || o[ol - 1] == "\n"){
if ((v = (u = ((out_context_word[last_tok] || o[ol - 1] == "\n")
&& !new_block[last_tok])) && !s[sl - 1].indexOf("{")
&& ol > scope) && !parse_mode)
switchToBlock(); // switch to block mode
if (u && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
if (v) {
o[ol++] = (o[ol - 2] != "\n" && block_autoappend[last_tok])
? "+''+"
: (nesting ? cf_str_output : cf_mode_output);
}
else if (block_autoappend[last_tok])
o[ol++] = "+";
// start markup mode with the markup-stack counting
last_ns = null, o[ol++] = '"', o[ol++] = "<", nesting++,
s[sl++] = scope | (parse_mode << 28), sl += 3,
s[sl - 2] = s[sl - 1] = 0;
segment = scope = ol - 1, parse_mode = 4;
}
else
o[ol++] = "<";
break;
case 14: // -------- < --------
o[ol++] = ">";
break;
case 15: // end
if (sl && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
break;
}
break;
case 2: // ========================== text parse_mode ==========================
switch (type) {
case 1: // -------- newline --------
line_no++, last_line = pos;
if (ol != scope && ol != segment) // only output when not first
o[ol++] = "\\n";
break;
case 2: // -------- misc --------
if (ol == segment) // segment connectors
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = unesc_txt[tok] || tok;
break;
case 3: // word
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
if (tok.charAt(tok.length-1)=='$'){
o[ol++] = tok.slice(0,-1);
o[ol++] = tok = '$';// fix word$[xpath]
}else o[ol++] = tok;
break;
case 5: // -------- stringquotes --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = (tok == '"') ? "\\\"" : "'";
break;
case 7: // -------- { -------- code mode
if (ol == segment) {
if (ol != scope )
o[ol++] = "+";
}
else
o[ol++] = "\"+", nesting || o_segs++;
s[sl++] = scope | 0x20000000, s[sl++] = o[ol++] = "{{",
nesting++, segment = scope = ol, parse_mode = 0;
break;
case 9: // -------- [ -------- xpath mode
last_model = null; // get xpath macro
if ((w = xpath_lut_text[last_tok]) && o[ol - 1] == last_tok) {
if (--ol - 1 == scope)
ol --; // remove first ""
}
else // only select c_xpathmode when nesting == 0
w = xpath_macro[(nesting || scope != ol) ? 0 : c_xpathmode];
if (ol != scope) {
o[ol] = (ol++ == segment) ? "+" : (nesting || o_segs++, "\"+");
if (!nesting)
o_segs++;
}
s[sl++] = scope | 0x20000000, s[sl++] = o[ol++] = w,
segment = scope = ol, nesting++, parse_mode = 3;
break;
case 15: // -------- end --------
if (sl)
throw {
t: "Unclosed " + s[sl-1] + " found at end in textmode",
p: pos
};
if (ol != scope && ol != segment)
o[ol++] = "\"", nesting || o_segs++;
break;
default: // -------- default --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = tok;
}
break;
case 3: // ========================== xpath parse_mode =========================
switch(type) {
case 0: // -------- whitespace --------
if (ol != scope){ // strip initial spaces\l
if (ol == segment)
o[ol++] = "+\"";
o[ol++] = tok;
}
break;
case 1: // -------- newline --------
line_no++, last_line = pos;
break;
case 2: // -------- misc --------
if (tok == ":" && last_tok == ":" && !xpath_axes[w = o[ol - 2]]
&& ((v = s[sl - 2]) >> 28) != 6) { // found model::xpath split
if (o[ol - 2] == '+"') // model is calculated
o[ol - 2] = o[ol - 1] = "", last_model = "#";
else {
o[ol - 1] = '"';
if (segment == scope) // model is normal name
last_model = o.slice(segment + 1, ol - 1).join("");
else // model is calculated
last_model = "#";
}
if (!(w = xpath_model[o[scope - 1]]))
throw {
t: "Invalid model found for: "+o[scope-1],
p: pos
};
o[scope - 1] = w, o[ol++] = ",", segment = scope = ol;
}
else {
if (tok == "," && (v = (s[sl - 2] >> 28)) <= 1) { // xpath is an array in code
ol = scope-1, u = str.slice(bt[scope] + 1, pos + 1);
// fix up stack to not be an xpath but an array
last_type = 9, parse_mode = v, o[ol++] = last_tok = "[";
s[sl - 2] = (s[sl - 2] & 0xfffffff) | (parse_mode << 28),
s[sl - 1] = last_tok, segment = scope = ol, nesting--;
if (!nesting)
o_xpaths--;
if (u.length > 1) { // ignore [, optimized escaping
bts.push(str); // push str so str always is the string in replace
(str = u).replace(parserx, parser); // reparse it
str = bts.pop(); // pop it again
}
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = unesc_lut[tok] || tok;
}
}
break;
case 3: // word
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
if (tok.charAt(tok.length-1)=='$'){
o[ol++] = tok.slice(0,-1);
o[ol++] = tok = '$';// fix word$[xpath]
}else o[ol++] = tok;
break
case 5: // -------- stringquotes --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
if (s[sl - 1] == "[") // strings only are used in [ ]
s[sl - 1] = tok;
else if (s[sl - 1] == tok) // close string
s[sl - 1] = "[";
if (tok == '"')
o[ol++] = "\\";
o[ol++] = tok;
break;
case 7: // -------- { --------
if (ol == segment) {
if (ol != scope)
o[ol++] = "+''+";
}
else
o[ol++] = "\"+";
s[sl++] = scope | 0x30000000, s[sl++] = o[ol++] = "{{",
nesting++, segment = scope = ol, parse_mode = 0;
if (last_model && s[sl - 3] != xpath_lut_text["$"]) {
o_xpathpairs.push(last_model, "#");
last_model = null, o_models++;
}
break;
case 9: // -------- [ --------
// lets see if we are an xpath
if (s[sl - 1] == "'" || s[sl - 1] == '"' ||
((last_type != 3 || last_tok=='$') && last_tok != ")" && last_tok != "]") ) {
if (last_model)
o_xpathpairs.push(last_model, "#"), o_models++;
last_model = null;
if ((w = xpath_lut_text[last_tok]) && o[ol - 1] == last_tok)
ol--;
else
w = xpath_macro[0];
if (ol == segment) {
if (ol != scope)
o[ol++] = "+";
}
else o[ol++] = "\"+";
s[sl++] = scope | 0x30000000, s[sl++] = o[ol++] = w, nesting++,
segment = scope = ol, parse_mode = 3;
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
s[sl++] = scope|0x60000000, s[sl++] = o[ol++] = "["; // keep track of [, abuse mode 6
}
break;
case 10: // -------- ] --------
sl--, parse_mode = (w = s[--sl]) >> 28, w = w & 0x0fffffff;
if (parse_mode == 6){ // was part of [] internally to xpath, see above
if (s[sl + 1] != "[")
throw {
t: "In xpath, cannot close " + s[sl + 1] + " with " + tok,
p: pos
};
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = "]";
parse_mode = 3;
}
else {
if (ol == scope ) {
if ((s[sl] >> 28) <= 1) // empty array in code
o[scope - 1] = "[", o[ol++] = "]";
else // empty xpath elsewhere
o[scope - 1] = o[ol++] = "\"" ;
segment = ol;
}
else {
//if( s[sl+1] != '[' )
// throw {t:"Unclosed string in xpath"+s[sl+1], p: pos};
if (ol != segment)
o[ol++] = "\"";
if (segment == scope){ // we might have an xpath name
v = o.slice(segment + 1, ol - 1).join("");
if (c_injectself && o[scope - 1] != "," // inject self
&& v != (u = v.replace(selfrx, "$1self::"))
&& s[sl + 1] != xpath_lut_text["$"]) {
o[scope+1] = v = u;
for (u = scope + 2; u < ol - 1; u++)
o[u] = "";
}
}
else {
if ((u = o[scope - 1]) != ",") {
v = "#";
if (c_injectself)// inject dyn self if dyn xpath
o[scope - 1] = u + "_injself(", o[ol++] = ")";
}
else
v = "";
}
if (s[sl + 1] != xpath_lut_text["$"] && v) {
o_xpathpairs.push(last_model, v); // only store if not _lng
if (last_model)
o_models++;
}
o[ol++] = ") ", segment = ol; // close xpath with ') ' marker
//logw("CLOSING XPATH"+o.join('#')+nesting);
if (parse_mode == 7) // attribute assign in xml mode
o[ol++] = "+\"\\\"", parse_mode = 4;
}
// lets output an xpath if we werent a language symbol
nesting--, last_model = null;
if (!nesting)
o_segs++, o_xpaths++;
}
scope = w;
break;
case 11: // -------- ( --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
s[sl++] = scope | 0x30000000, // keep track of () in xpath
s[sl++] = o[ol++] = "(";//, last_model = null;
break;
case 12: // -------- ) --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
if (type_close[v = s[--sl]] != (o[ol++] = tok))
throw {
t: "Cannot close " + v + " with " + tok,
p: pos
};
scope = s[--sl] & 0xfffffff;
break;
case 15: // -------- end --------
throw {
t: "Unexpected end whilst parsing xpath",
p: pos
};
break;
default: // -------- default --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = tok;
break;
}
break;
case 4: // =========================== xml parse_mode ==========================
switch (type) {// stack: '<'sl+4,outside=0, ''sl-4 '>'sl-2,outside=1 '/>'sl-4,outside=1
case 0: // -------- whitespace --------
if (ol == segment)
o[ol++] = "+\"";
o[ol++] = " ", last_type = 0;
break;
case 1: // -------- newline --------
if (ol == segment)
o[ol++] = "+\"";
line_no++, last_line = pos, o[ol++] = "\\n", last_type = 1;
break;
case 2: // -------- misc --------
if (ol == segment)
o[ol++] = "+\"";
if (tok == "/" && last_tok == "<") {
sl -= 4; // closing tag, drop stacklevel by 4
if (s[sl] || s[sl + 2])
throw {
t: "Unexpected closing tag whilst parsing xml",
p: pos
};
}
else if (tok == ":" && last_type == 3 && o[ol - 2] == "<")
last_ns = last_tok; // found a namespace item in a tag
o[ol++] = unesc_txt[tok] || tok;
break;
case 3: // word
if (ol == segment)
o[ol++] = "+\"";
if (tok.charAt(tok.length-1)=='$'){
o[ol++] = tok.slice(0,-1);
o[ol++] = tok = '$';// fix word$[xpath]
}else o[ol++] = tok;
break
case 5: // -------- stringquotes --------
if (ol == segment)
o[ol++] = "+\"";
if (tok == '"')
o[ol++] = "\\";
o[ol++] = tok;
break;
case 6: // -------- comment --------
if (tok == "//" && !s[sl - 1]) {
if (ol == segment)
o[ol++] = "+\""; // < char ups stack by 4, outside= 0
o[ol++] = tok;
}
else {
if (tok == "*/")
throw {
t: "Unmatched comment "+tok,
p: pos
};
last_cmt_mode = parse_mode, last_cmt_tok = last_tok,
last_cmt_type = last_type, parse_mode = 6, start_tok = tok;
}
break;
case 13: // -------- < --------
last_ns = null;
if (ol == segment)
o[ol++] = "+\""; // < char ups stack by 4, outside= 0
o[ol++] = tok, s[sl] = s[sl + 2] = 0, sl += 4, s[sl - 1]=0;
break;
case 14: // -------- > --------
if (ol == segment)
o[ol++] = "+\"";
o[ol++] = tok;
if (last_tok != "<") {
if (last_tok == "/") {
sl -= 4; // self close tag /> drops stack -4
if (s[sl + 2])
throw {
t: "Unexpected / whilst parsing xml",
p: pos
}
if (o[ol - 3] == "<") // remove empty > from output
ol -= 2, o[ol - 1] = "";
}
else
sl -= 2; // nets stackdepth of 2
if (s[sl]) { // end of xml mode
nesting--, o[ol++] = "\"", scope = s[sl], segment = ol,
parse_mode = scope >> 28, scope = scope & 0x0fffffff;
}
else
s[sl - 1] = 1; // we are outside a tag, flag it on the stack
}
else // remove empty <> from output
ol--, o[ol - 1] = "";
break;
case 9: // -------- [ -------- xpath mode
last_model = null;
if (last_tok == "!" && o[ol - 2] == "<" && !s[sl - 1]) { // CDATA mode
o[ol++] = tok, s[sl++] = scope | (parse_mode << 28);
s[sl++] = "]]>", scope = segment = ol - 1;
nesting++, parse_mode = 5;
}
else {
if (s[sl - 1]) { // we are outside a tag
if ((v = xpath_lut_node[last_tok]))
ol --;
else
v = xpath_macro[c_elemxpath];
s[sl++] = scope | 0x40000000
}
else {
s[sl++] = scope | 0x40000000
if ((v = xpath_lut_attr[last_tok])) {
ol--;
if (o[ol - 1] == "=")
last_tok = "=";
}
else
v = xpath_macro[last_ns ? c_statexpath : 8];
if (last_tok == "=")//0x7 flags xpath-in-missing-quotes
o[ol++] = "\\\"", s[sl - 1] = scope | 0x70000000;
}
o[ol] = (ol++ == segment) ? "+''+" : "\"+";
nesting++, s[sl++] = o[ol++] = v,
segment = scope = ol, parse_mode = 3;
}
break;
case 7: // -------- { -------- code mode
if ( !s[sl - 1] && last_tok == "=") // 0x7 flags code-in-missing-quotes
o[ol++] = "\\\"", s[sl++] = scope | 0x70000000;
else
s[sl++] = scope | 0x40000000
o[ol] = (ol++ == segment) ? "+''+" : "\"+";
s[sl++] = o[ol++] = "{{", nesting++;
segment = scope = ol, parse_mode = 0;
break;
default:
if (ol == segment)
o[ol++] = "+\"";
o[ol++] = tok;
break;
case 15: // -------- end --------
throw {
t: "Unexpected end whilst parsing xml",
p: pos
};
break;
}break
case 5: // ========================== string parse_mode ========================
switch (type) {
case 1: // -------- newline --------
line_no++, last_line = pos;
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = "\\n";
break;
case 2: // -------- misc --------
if (tok == "/" && s[sl - 1] == "/") { // regexp closing character
o[ol++] = "/", scope = s[sl -= 2], segment = ol,
parse_mode = scope >> 28,
scope = scope & 0x0fffffff, nesting--;
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = (s[sl - 1] != "/" && unesc_str[tok]) || tok;
}
break;
case 3: // word
if (ol == segment)
o[ol] = (ol++ == scope) ? "" : "+\"";
if (tok.charAt(tok.length-1)=='$'){
o[ol++] = tok.slice(0,-1);
o[ol++] = tok = '$';// fix word$[xpath]
}else o[ol++] = tok;
break
case 5: // -------- stringquotes --------
if (s[sl - 1] == tok) { // closed by matching quote
if (scope != segment) // string is segmented, output )
o[ol] = (ol++ != segment) ? (tok + ")") : ")";
else
o[ol++] = tok; // else just close
scope = s[sl -= 2], segment = ol, parse_mode = scope >> 28;
scope = scope & 0x0fffffff, nesting--;
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "\"" : "+\"";
o[ol++] = tok == '"' ? "\\\"" : tok;
}
break;
case 6: // -------- default --------
if (s[sl - 1] == "/" && tok == "*/") { // caught faux comment in regexp /a*/, is close
o[ol++] = "*/", scope = s[sl -= 2], segment = ol,
parse_mode = scope >> 28, scope = scope & 0x0fffffff, nesting--;
}
else {
if (ol == segment)
o[ol] = (ol++ == scope) ? "" : "+\"";
o[ol++] = tok;
}
break;
case 7: // -------- { -------- code mode
if (s[sl - 1] != "'" && s[sl - 1] != "/") {
if (s[sl - 1] == '"')
o[scope] = '("';
if (ol == segment) {
if (ol != scope)
o[ol++] = "+";
}
else
o[ol++] = "\"+";
s[sl++] = scope | 0x50000000, o[ol++] = s[sl++] = "{{",
nesting++, segment = scope = ol, parse_mode = 0;
}
else
o[ol++] = tok;
break;
case 9: // -------- [ -------- xpath mode
if (s[sl - 1] != "'" && s[sl - 1] != "/" // ignore in '' and CDATA[, else xpath
&& (s[sl - 1] == '"' && (o[scope] = '("') || ol != scope + 2
|| last_tok != "CDATA") ) {
last_model = null;
if ((w = xpath_lut_text[last_tok]) && o[ol - 1] == last_tok)
ol--;
else
w = xpath_macro[0]
if (ol != scope)
o[ol] = (ol++ == segment) ? "+" : "\"+";
s[sl++] = scope | 0x50000000, s[sl++] = o[ol++] = w,
segment = scope = ol, nesting++, parse_mode = 3;
}
else
o[ol++] = tok;
break;
case 14: // -------- > --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "" : "+\"";
o[ol++] = tok;
if (s[sl - 1] == "]]>" && last_tok == "]" && o[ol - 3]=="]") { // check if CDATA close
scope = s[sl -= 2], parse_mode = scope >> 28;
scope = scope & 0x0fffffff, nesting--;
sl -= 4; // close the tag since we came from XML mode
if (s[sl]) // was last tag, jump up the stack one more.
nesting--, o[ol++] = "\"", scope = s[sl], segment = ol,
parse_mode = scope >> 28, scope = scope & 0x0fffffff;
else
s[sl - 1] = 1;
}
break;
case 15: // -------- end --------
throw {
t: "Unexpected end whilst parsing string",
p: pos
};
break;
default: // -------- default --------
if (ol == segment)
o[ol] = (ol++ == scope) ? "" : "+\"";
o[ol++] = tok;
break;
}
break;
case 6: // ========================= comment parse_mode ========================
switch (type) {
case 1: // -------- newline --------
line_no++, last_line = pos;
if (start_tok == "//")
parse_mode = last_cmt_mode,
tok = last_tok = last_cmt_tok,
type = last_type = last_cmt_type;
break;
case 6: // -------- comment --------
if ((start_tok == "/*" && tok == "*/")
|| (start_tok == "")) {
parse_mode = last_cmt_mode,
tok = last_tok = last_cmt_tok,
type = last_type = last_cmt_type;
}
break;
case 15: // -------- end --------
if (start_tok != "//"){
throw {
t: "Unexpected end whilst parsing comment",
p: pos
}
} else {
parse_mode = last_cmt_mode,
tok = last_tok = last_cmt_tok,
type = last_type = last_cmt_type;
if (sl && !s[sl - 1]) { // close = macro
o[ol - 1] == "\n" && (o[ol - 1] = ""), o[ol++] = ")",
o[ol++] = "\n", v = 1, sl -= 2;
}
};
break;
}
break;
}
if (type > 1)
last_tok = tok, last_type = type;
}
this.lastCode = function(){
if (typeof(o) == "object")
return o.join("");
return o;
};
function handleError(e, last_line, part, linenr) {
// TODO: make a proper APF exception with this information:
if (e.t) {
throw new Error(apf.formatErrorString(0, null,
"Parsing live markup source",
"Error whilst parsing: " + e.t + " on line:"+ line_no
+ " col:" + (e.p - last_line - 2)
+ (part ? (" part: " + part) : "") + "\n" + str));
}
else {
throw new Error(apf.formatErrorString(0, null,
"Compiling live markup function on line " + linenr,
"Error whilst compiling: " + e.message
//+ "\nStack Trace:\n" + e.stack
+ "\nInput:\n" + str
+ "\nGenerated:\n" + apf.lm.lastCode()));
}
}
/**
* description of the method.
* Remarks:
* function{type:1,xpaths:[ model,name], props: ['obj.propname','obj2.otherpropname'], asyncs=1}
* this is a normal compiled function with extra properties
* if the xpath model and/or name is '#' it means it is a runtime calculated modelname or xpath.
* obj{type:2, str:str} it was single string by cfg option !alwayscode
* obj{type:3, xpaths:[ model, name ] } it was a single xpath by cfg simplexpath
*
* @param {String} str the code to compile
* @param {Object} options
* Properties:
* {Boolean} withopt creates with(_w){ code using an options block. (reqd for precall)
* {Boolean} precall wraps 1 async call into precallstore. call with _w._pc = 1 to precall, second time to execute.
* {Boolean} alwayscb always call callback function, even if not async
* {Boolean} nostring even generate code for a simple string
* {Number} xpathmode default type of root level xpath in code mode
* Possible values:
* 0: value
* 1: value with createnode
* 2: node
* 3: node with createnode
* 4: nodes
* 5: xpathobj returns a {model:model,xpath:xpath} object from xpaths
* {Boolean} parsecode start in codemode. if 0 its textmode.
* {Boolean} nostate dont' use _valst macro on [xpath] in namespaced xml.
* {Boolean} liveedit use the _valed macro for [xpath] in namespaced xml.
* {Boolean} langedit use of language items in namespaced xml text.
* {Boolean} injectself injects self:: to suitable xpaths
* {Boolean} event its an event thats being compiled, results in no returnvalue for this function.
* and the first argument is now an 'e' for the event object.
* {Boolean} funcglobal all functions defined in LM are made global
*
* @return {Function} returns a function with extra properties
* Properties:
* {Number} type description
* Possible values:
* 1 Function return type
* 2 Parsed data is a pure string
* 3 Function return type, but its a single xpath
* 4 Function return type, but single propxs
* {Array} xpaths array of [model,xpath, model,xpath] pairs if model
* or xpath is '#', its dynamic if model is null its a local xpath
* {Number} models number of models
* {Array} props description
* {Number} asyncs description
* {String] str optional, returned with type 2
*/
var cache = {},
emptyCfg = {};
this.resetCache = function(){
cache = {};
};
var lmcache_rx = /^\s*~~(c\d+)~~/;
this.compile = function(istr, cfg) {
if (!cfg)
cfg = emptyCfg;
if (istr == null || !istr.length) {
return (cfg.nostring || cfg.event)?function(){return istr}:{
type: 2,
str: istr
};
}
// lets see if we need to fetch precompiled cachemarker
var c, f, is_single_prop;
if (istr.charAt(0)=="~" && (c=istr.match(lmcache_rx))){
if (c=apf.lm_exec[c[1]]) return c;
alert("ERROR, undefined live markup cache marker found:"+istr);
return {type:2,str:istr};
}
var key = (cfg.xpathmode | (cfg.withopt && 0x10) | (cfg.precall && 0x20)
| (cfg.alwayscb && 0x40) | (cfg.nostring && 0x80) | (cfg.parsecode && 0x100)
| (cfg.nostate && 0x200) | (cfg.liveedit && 0x400)| (cfg.langedit && 0x800)
| (cfg.injectself && 0x1000) | (cfg.event && 0x2000) | (cfg.funcglobal && 0x4000)) + istr;
if (c = cache[key])
return c;
c_injectself = cfg.injectself, c_xpathmode = cfg.xpathmode||0,
c_statexpath = cfg.nostate ? 0 : 6, c_elemxpath = 0;
c_export = cfg.funcglobal?"self":(cfg.withopt?"_w":null);
c_process_async = !cfg.event;
xpath_macro.edit = cfg.liveedit ? "_argwrap(_n," : "_argwrap(_n,";//"_val(_n,";
macro_o.edit = cfg.liveedit ? macro_o._editlm : macro_o._editnormal;
xpath_lut_node = cfg.langedit ? xpath_lut_node_langedit : xpath_lut_node_normal;
o_props = {}, o_xpathpairs = [], s = [], o = ["","","",""], str = istr,
str_len = str.length;
ol = scope = segment = o.length,
o_segs = o_xpaths = o_asyncs = o_models = nesting = line_no = last_type = last_line = 0;
if (cfg.parsecode) {
parse_mode = 0, sl = 2, s[0] = ol, s[1] = "{{", last_tok = "{",
cf_mode_output = cfg.event ? "" : (c_xpathmode <= 1 ? cf_str_output : cf_obj_output);
}
else
parse_mode = 2, sl = last_tok = 0, cf_mode_output = cf_str_output;
if (cfg.nothrow) {
str.replace(parserx, parser);
}
else {
try {
str.replace(parserx, parser);
}
catch (e) {
handleError(e, last_line);
return null;
}
}
if (cfg.parsecode) {
if (nesting || s[sl - 1].length == 1)
handleError({
t: "Unclosed " + s[sl-1] + " found at end in codemode",
p: str_len
},last_line);
if (segment!=ol)
o_segs++
}else if( (ol==7 || ol==8) && o_segs == 1) {
is_single_prop = 0;
for (c in o_props)is_single_prop++;
if (is_single_prop!=1)is_single_prop = 0;
}
if ((!cfg.nostring && !cfg.event)&& (parse_mode == 2 && segment == 4 || ol == 4)) {
return {
type: 2,
str: o.slice(5, -1).join("").replace(/\\n/g, "\n").replace(/\\"/g, '"')
}; // string only
}
if (o_asyncs || cfg.alwayscb) {
if (cfg.event) { // event
if (parse_mode == 1)
o[3] = "";
o[ol++] = cc_o_blk_ce;
}
else if (c_xpathmode) { // object return
if (parse_mode == 1) {
o[3] = (o[3] != cf_block_o) ? cc_o_blk_o : cc_o_blk_ob,
o[ol++] = cc_o_blk_cb;
}
else
o[3] = cc_o_cb_o, o[ol++] = cc_o_cb_c;
}
else { // value return
if (parse_mode == 1)
o[3] = (o[3] != cf_block_o) ? cc_v_blk_o : cc_v_blk_ob,
o[ol++] = cc_v_blk_cb;
else
o[3] = cc_v_cb_o, o[ol++] = cc_v_cb_c;
}
if (o_asyncs) {
// for parse_mode == 1 we can squeeze in before [3] and cb close
// else we put var _r= in 3 and put our ending last and put
// the cb at the end
if (parse_mode==1) {
if (cfg.precall)
o[2] = cc_pc_o, o[ol-1] = cc_pc_c + o[ol-1];
else
o[2] = cc_async_o, o[ol-1] = cc_async_c + o[ol-1];
}else{
o[ol++] = o[3] + '_r' + o[ol-2];
if (cfg.precall)
o[2] = cc_pc_o, o[3] = cc_o_blk_o, o[ol-2] = cc_pc_c;
else
o[2] = cc_async_o, o[3] = cc_o_blk_o, o[ol-2] = cc_async_c;
}
}
if (cfg.withopt)
o[1] = cc_opt_o, o[ol++] = cc_opt_c;
o[0] = cfg.event
? cc_fe_async_o
: ((c_xpathmode == 1 || c_xpathmode == 3) ? cc_fc_async_o : cc_f_async_o);
o[ol++] = cc_f_c;
}
else {
if (cfg.event) { // event
if (parse_mode == 1)
o[3] = "";
}
else if (c_xpathmode) { // object return
if (parse_mode == 1) {
o[3] = (o[3] != cf_block_o) ? cc_o_blk_o : cc_o_blk_ob,
o[ol++] = cc_o_blk_c;
}
else
o[3] = cc_o_ret_o, o[ol++] = cc_o_ret_c;
}
else { // value return
if (parse_mode == 1) {
o[3] = (o[3] != cf_block_o) ? cc_v_blk_o : cc_v_blk_ob,
o[ol++] = cc_v_blk_c;
}
else
o[3] = cc_v_ret_o, o[ol++] = cc_v_ret_c;
}
if (cfg.withopt)
o[2] = cc_opt_o, o[ol++] = cc_opt_c;
o[0] = cfg.event
? (cfg.withopt ? cc_fe_opt_o : cc_fe_o)
: (cfg.withopt
? ((c_xpathmode == 1 || c_xpathmode == 3) ? cc_fc_opt_o : cc_f_opt_o)
: ((c_xpathmode == 1 || c_xpathmode == 3) ? cc_fc_o : cc_f_o));
o[ol++] = cc_f_c;
}
var code = "with(apf.nameserver.lookup.all){\n" + o.join("") + "\n}";
if (cfg.nothrow) {
f = apf.lm_exec.compile(code);
}
else {
try {
f = apf.lm_exec.compile(code);
}
catch (e) {
if (!apf.isIE) {
var oErr = window.onerror;
window.onerror = function(x,y,line) {
window.onerror = oErr;
handleError(e, last_line, null, line);
return true;
}
apf.include("", "", null, o.join(""));
window.onerror = oErr;
}
else {
handleError(e,last_line);
}
return null;
}
}
f.type = (o_segs == 1 && o_xpaths == 1) ? 3 : (is_single_prop?4:1);
f.xpaths = o_xpathpairs, f.models = o_models,
f.props = o_props, f.asyncs = o_asyncs;
cache[key] = f;
return f;
};
/**
* description of the method.
* Remarks:
* @param {String} str the code to compile
* @param {Object} options
* Properties:
* {Boolean} node tries to return a node, used as a dual-compile with 'normal mode'
*
* @return {Function} returns a function with extra properties
* Properties:
* {Number} type description
* Possible values:
* 1 Function return type
* 2 Parsed data is a pure string
* 3 Function return type, but its a single xpath
* {Array} xpaths array of [model,xpath, model,xpath] pairs if model
* or xpath is '#', its dynamic if model is null its a local xpath
* {Number} models number of models
* {Array} props description
* {Number} asyncs description
* {String] str optional, returned with type 2
*/
this.compileMatch = function(strarray, cfg) {
if (!cfg)
cfg = emptyCfg;
o_props = {}, o_xpathpairs = [], o = [cc_f_match_o, cc_m_o], s = [],
nesting = 0, ol = o.length, xpath_lut_node = xpath_lut_node_normal;
for (var st, ob, i = 0, j = strarray.length; i < j; i += 2) {
if (str = strarray[i]) {
str_len = s.length, c_xpathmode = 2;
if (i)
o[ol++] = cc_m_brk;
o[ol++] = "";
s[0] = ob = ol = scope = segment = o.length, cf_mode_output = cf_obj_output;
line_no = last_type = o_segs = o_xpaths = o_asyncs = parse_mode = last_line = 0;
sl = 2, s[1] = "{{", last_tok = "{";
c_injectself = 1;
if (cfg.nothrow) {
str.replace(parserx, parser);
}
else {
try {
str.replace(parserx, parser);
}
catch (e) {
handleError(e,last_line);
return null;
}
}
if (nesting || s[sl - 1].length == 1)
handleError({
t: "Unclosed " + s[sl - 1] + " found at end in codemode",
p: str_len
});
if (o_asyncs)
handleError({t:"Asynchronous calls not supported in match/value"});
if (parse_mode == 1) { // block mode
o[ob - 1] = (o[ob - 1] != cf_block_o) ? cf_mode_output : "",
o[ol++] = cc_m_m_blk;
}
else // value mode
o[ob-1] = cc_m_m_value_o, o[ol++] = cc_m_m_value_c;
}
if (str = strarray[i + 1]) {
str_len = s.length;
if (!strarray[i] && i)
o[ol++] = cc_m_brk;
o[ol++] = "";
ob = ol = scope = segment = o.length, cf_mode_output = cf_str_output;
c_xpathmode = c_injectself = last_tok = sl = line_no = o_segs = o_xpaths =
last_type = o_asyncs = last_line = 0;
if (cfg.node)
c_xpathmode = 2;
parse_mode = 2, c_injectself = 0;
if (cfg.nothrow) {
str.replace(parserx, parser);
}
else {
try {
str.replace(parserx, parser);
}
catch (e) {
handleError(e,last_line);
return null;
}
}
if (o_asyncs)
handleError({t:"Asynchronous calls not supported in match/value"});
if (cfg.node) {
if (parse_mode == 2 && segment == ob || ol == ob)
o[ob-1] = cc_m_n_string;
else
o[ob-1] = cc_m_n_o, o[ol++] = cc_m_n_c;
}else{
if (parse_mode == 2 && segment == ob || ol == ob)
o[ob-1] = cc_m_v_string;
else
o[ob-1] = cc_m_v_o, o[ol++] = cc_m_v_c;
}
if (strarray[i])
o[ol++] = cc_m_c;
else
break;
}
else {
if (!strarray[i])
handleError({t:"Both match and value are empty"});
if (cfg.node)
o[ol++] = cc_m_n_ret;
else
o[ol++] = cc_m_v_ret;
c_xpathmode = 2;
o[ol++] = cc_m_c;
}
}
o[ol++] = cc_f_c;
var f;
if (cfg.nothrow) {
f = apf.lm_exec.compile(o.join(""));
}
else {
try{
f = apf.lm_exec.compile(o.join(""));
}
catch (e) {
handleError(e,last_line);
return null;
}
}
f.type = 1, f.xpaths = o_xpathpairs,
f.props = o_props, f.asyncs = o_asyncs;
return f;
};
this.setWarnLevel = function(lvl) {
apf.lm_exec.setWarnLevel(lvl);
};
this.parseExpression = function(istr, cfg) {
if (!cfg)
cfg = emptyCfg;
o_props = {}, o_xpathpairs = [], o = [], s = [],
nesting = 0, xpath_lut_node = xpath_lut_node_normal;
str = istr, str_len = str.length;
ob = ol = scope = segment = o.length, cf_mode_output = cf_str_output;
c_xpathmode = c_injectself = last_tok = sl = line_no = o_segs = o_xpaths =
last_type = o_asyncs = last_line = 0;
parse_mode = 2;
if (cfg.nothrow) {
str.replace(parserx, parser);
}
else {
try {
str.replace(parserx, parser);
}
catch (e) {
handleError(e,last_line);
return null;
}
}
return o.join('');
}
})();
// apf lm_exec makes sure there is no scope pollution for eval'ed live markup.
apf.lm_exec = new (function(){
var wlvl = 1; // 0: no warnings 1: language/models missing, 2:nodes missing, 3:all failed xpaths
//warning functions
this.setWarnLevel = function(lvl) {
wlvl = lvl;
};
function wxpath(x, t) {
apf.console.warn("Live Markup warning in " + t + ", no results for xpath: '" + x + "'");
}
function wnode(x, t) {
apf.console.warn("Live Markup warning in " + t + ", xpath on null node: '" + x + "'");
}
function wmodel(m, x, t) {
apf.console.log("Live Markup warning in " + t + ", xpath on empty model: '" + m + "' xpath: '" + x + "'");
}
function wlang(x, t) {
apf.console.log("Live Markup warning in " + t + ", language symbol not found: '" + x + "'");
}
// xml parse function used by all livemarkup objects
function xmlParse(str) {
var n = apf.getXmlDom("<_apflmlist_>" + str + "");
if (!n || !(n = n.documentElement))
return null;
return (n.firstChild == n.lastChild) ? n.firstChild : n;
}
// value of node by xpath
function __val(n, x) {
if (!n)
return ("")
return apf.escapeXML((n = (!n.nodeType && n || (n = n.selectSingleNode(x)) //!= 1
&& (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n)))
&& n.nodeValue || (""));
}
var __valattrrx = /(["'])/g;
function __valattrrp(m,a) {
return m=='"'?""":"'";
}
function __valattr(n, x) {
if (!n)
return ("")
return apf.escapeXML((n = (n.nodeType != 1 && n || (n = n.selectSingleNode(x))
&& (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n)))
&& n.nodeValue.replace(__valattrrx,__valattrrp) || (""));
}
// value of model node by xpath
function __valm(m, x) {
var n;
if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
|| ((n = apf.nameserver.lookup.model[m]) && n.data)))
|| (m.$isModel ? m.data : (m.charAt ? 0 : m))))
return ("");
return (n = (n.nodeType != 1 && n || (n = n.selectSingleNode(x))
&& (n.nodeType != 1 && n || (n = n.firstChild) && n.nodeType!=1 && n)))
&& n.nodeValue || ("");
}
function __nod(n, x){ // node by xpath
return n ? n.selectSingleNode(x) : (null);
}
function _nods(n, x){ // array of nodes by xpath
return n ? n.selectNodes(x) : ([]);
}
function __nodm(m, x){ // node of model by xpath
var n;
if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
|| ((n = apf.nameserver.lookup.model[m]) && n.data)))
|| (m.$isModel ? m.data : (m.charAt ? 0 : m))))
return (null);
return n.selectSingleNode(x);
}
function _nodsm(m, x){ // array of nodes from model by xpath
var n;
if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
|| ((n = apf.nameserver.lookup.model[m]) && n.data)))
|| (m.$isModel ? m.data : (m.charAt ? 0 : m))))
return ([]);
return n.selectNodes(x);
}
function __cnt(n, x){ // count nodes by xpath
return n ? n.selectNodes(x).length:(0);
}
function __cntm(m, x){ // count nodes from model by xpath
var n;
if (!m || !(n = (m.charAt && ((m.charAt(0) == "<" && xmlParse(m))
|| ((n = apf.nameserver.lookup.model[m]) && n.data)))
|| (m.$isModel ? m.data : (m.charAt ? 0 : m))))
return (0);
return n.selectNodes(x).length;
}
function _xpt(n, x){ // return the query wrapped in an object
return {
xpath: x,
toString: function(){
return "LM Xpath object: " + this.x
}
};
}
function _xptm(m, x){ // return the query with model wrapped in an object
if (m && !m.$isModel) {
var node = m;
m = apf.xmldb.findModel(m);
x = apf.xmlToXpath(node, m.data) + "/" + x;
}
return {
model: m,
xpath: x,
toString: function(){
return "LM Xpath object with model: " + this.x
}
};
}
//----- the following functions are combined model and normal mode ------
function _xml(n, m, x){ // serialize node by xpath via .xml
if (n) x = m;
else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
((n = apf.nameserver.lookup.model[m]) && n.data))) ||
(m.$isModel?m.data:(m.charAt?0:m))))
return ("");
return (n && (n = n.selectSingleNode(x))) && n.xml ||
("");
}
function _xmls(n, m, x){ // serialize nodes by xpath with .xml concatenated
if (n) x = m;
else if (!m || !(n=(m.charAt && ((m.charAt(0)=="<" && xmlParse(m)) ||
((n = apf.nameserver.lookup.model[m]) && n.data))) ||
(m.$isModel?m.data:(m.charAt?0:m))))
return ("");
for (var i = 0,j = ((n=n.selectNodes(x))).length,o = [];i' + ((n?__val(n,m):__valm(m,x)) || " ") + '';
}
// function _edit(n, opts) {
// return '' + ((n?__val(n,m):__valm(m,x)) || " ") + '';
// }
function _argwrap(n,x) {
return [n,x];
}
function _argwrapm(m,x) {
return [0,m,x];
}
function _valedx(editMode, args, opt){ // wrap a value with editable div
args[3] = opt;
args[4] = editMode;
return _valed.apply(this, args);
}
function _valed(n, m, x, options, editMode){ // wrap a value with editable div
var res = (n?__val(n,m):__valm(m,x));
if (options && options.multiline && options.editor != "richtext")
res = res.replace(/\n/g, " ");
if (editMode !== false) {
var value = res || options && options.initial || " ";
if (!options || !options.richtext)
value = apf.htmlentities(value);
if (options && options.multiline)
value = value
.replace(/<br ?\/?>/g, " ")
.replace(/<(\/?div)>/g, "<$1>");
return '