Simple demo of the tree control. See the source code for this page for more
-information on how the component is instantiated and stylized. Open the web
-console to see some of the events.
The trees below are rendered with two different styles. The tree on the
+left uses the component's default style, which is defined inline
+(no stylesheet required). The tree on the right uses a stylesheet-based
+style backed by an image sprite (tree.png). Open the web console to see events.
+
+
+
+
+
+
Default style
+
Inline SVG icons — no stylesheet needed
+
+
+
+
+
Windows classic style
+
CSS classes + tree.png image sprite
+
+
+
+
+
+
+
JavaXT Themes
+
The same tree styled with the JavaXT themes (themes/default.css and
+themes/dark.css). The nodes, leaves and connectors are drawn entirely with CSS
+(no image sprite) and follow the active theme. Each theme is shown in its own
+iframe so the two stylesheets don't collide.
+
+
+
+
+
+
Default theme
+
themes/default.css — javaxt-tree
+
+
+
+
+
Dark theme
+
themes/dark.css — javaxt-tree
+
+
+
+
+
+
+
diff --git a/demos/tree/tree-theme.html b/demos/tree/tree-theme.html
new file mode 100644
index 0000000..6bf2fff
--- /dev/null
+++ b/demos/tree/tree-theme.html
@@ -0,0 +1,69 @@
+
+
+
+
+Themed Tree
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/button/Button.js b/src/button/Button.js
index 7209e8e..81f977d 100644
--- a/src/button/Button.js
+++ b/src/button/Button.js
@@ -1,678 +1,775 @@
-if(!javaxt) var javaxt={};
-if(!javaxt.dhtml) javaxt.dhtml={};
-
-//******************************************************************************
-//** Button Class
-//******************************************************************************
-/**
- * Custom button control. The button has 3 parts: icon, label, and an arrow.
- *
- ******************************************************************************/
-
-javaxt.dhtml.Button = function(parent, config) {
- this.className = "javaxt.dhtml.Button";
-
- var me = this;
- var defaultConfig = {
-
- /** Text label for the button.
- */
- label: null,
-
-
- /** If true, the button will initially appear in a selected state and the
- * isSelected() method will return true. Default is false.
- */
- selected: false,
-
-
- /** If true, the button will initially appear disabled and the
- * isDisabled() method will return true. Default is false.
- */
- disabled: false,
-
-
- /** If true, the button will be rendered as a toggle button. Default is
- * false, unless the button has a menu.
- */
- toggle: false,
-
-
- /** If true, will create a drop-down menu for the button. Components such
- * as buttons or custom DOM elements can be added directly to the menu.
- * See getMenuPanel() for more information.
- */
- menu: false,
-
-
- /** Sets the position of the menu panel. Options are "bottom" or "right".
- */
- menuAlign: "bottom",
-
-
-
- /** Used to set the "display" style attribute for the outer DOM element.
- * This is not commonly ued. Default is "inline-block".
- */
- display: "inline-block",
-
-
- /** Used to set the width of the button. This property is optional.
- */
- width: null,
-
-
- /** Used to set the height of the button. This property is optional.
- */
- height: null,
-
-
- /** Used to set the icon position relative to the button label. Options
- * are "left" or "right". Note that the icon style is set in the style
- * config. The icon style should not be used to control whether the
- * icon appears to the left or right of the label. Use this config
- * instead.
- */
- iconAlign: "left",
-
-
- /** Style for individual elements within the component. Note that you can
- * provide CSS class names instead of individual style definitions.
- */
- style:{
-
- button: {
- border: "1px solid #cccccc",
- borderRadius: "3px",
- background: "#F6F6F6",
- cursor: "pointer",
- padding: "3px 7px",
- margin: "0px",
- color: "#2b2b2b"
- },
-
- label: {
- fontFamily: "helvetica,arial,verdana,sans-serif",
- fontSize: "14px",
- whiteSpace: "nowrap"
- },
-
- icon: {
-
- },
-
- arrow: {
-
- },
-
- select: {
- background: "#007FFF",
- border: "1px solid #003EFF",
- color: "#FFFFFF"
- },
-
- hover: {
- background: "#ededed"
- },
-
-
- menu: {
- border: "1px solid #cccccc",
- background: "#F6F6F6",
- cursor: "pointer",
- padding: "3px 3px",
- zIndex: "1"
- }
-
- },
-
-
- /** Sound to play when the button is clicked
- */
- sound: null
- };
-
- var mainDiv;
- var mask, menu;
- var icon, label, arrow;
-
-
- //**************************************************************************
- //** Constructor
- //**************************************************************************
- var init = function(){
-
- if (typeof parent === "string"){
- parent = document.getElementById(parent);
- }
- if (!parent) return;
-
-
- //Clone the config so we don't modify the original config object
- var clone = {};
- merge(clone, config);
-
-
- //Merge clone with default config
- merge(clone, defaultConfig);
- config = clone;
-
-
- //Get icon alignment
- var iconAlignment = config.style.iconAlign; //legacy
- if (!iconAlignment) iconAlignment = config.iconAlign; //preferred
- if (iconAlignment!=="right") iconAlignment = "left";
-
-
- //Get menu alignment
- var menuAlignment = config.style.menuAlignment; //legacy
- if (!menuAlignment) menuAlignment = config.menuAlign; //preferred
- if (menuAlignment!=="right") menuAlignment = "bottom";
-
-
- //Update arrow style as needed
- if (config.menu===true){
- var arrowDefined = false;
- for (var key in config.style.arrow){
- if (config.style.arrow.hasOwnProperty(key)){
- arrowDefined = true;
- break;
- }
- }
-
- if (!arrowDefined){
- if (menuAlignment==="bottom"){
- config.style.arrow = {
- width: 0,
- height: 0,
- borderLeft: "5px solid transparent",
- borderRight: "5px solid transparent",
- borderTop: "5px solid #575757",
- marginLeft: "10px"
- };
- config.style.arrowSelect = {
- borderTop: "5px solid #FFFFFF"
- };
- }
- }
- }
-
-
-
- //Create outer div used to hold the button, mask, and menu
- var outerDiv = createElement('div', parent);
- outerDiv.className = "javaxt-button";
- outerDiv.style.display = config.display;
- if (config.width){
- if (typeof config.width === "string"){
- outerDiv.style.width = config.width;
- }
- else{
- outerDiv.style.width = config.width + "px";
- }
- }
- if (config.height){
- if (typeof config.height === "string"){
- outerDiv.style.height = config.height;
- }
- else{
- outerDiv.style.height = config.height + "px";
- }
- }
- outerDiv.style.position = "relative";
- if (config.hidden===true){ //legacy config...
- outerDiv.style.visibility = 'hidden';
- outerDiv.style.display = 'none';
- }
- me.el = outerDiv;
-
-
-
-
- //The button is implemented using a simple HTML table with 3 columns. The
- //left and right columns are for icons and the center column is for the
- //button label. The width of the center column is set to 100% and the
- //width of the left and right columns are defined by the "icon" and
- //"arrow" styles. Unfortunately, some browsers seem to have issues
- //rendering the table correctly inside of a div when the outerDiv's
- //display style set to "inline-block". For example, Mobile Safari will
- //completely ignore the "inline-block" style and stretch the button to
- //100% of the available width. In Chrome, if the left or right columns has
- //a width, the "inline-block" style is ignored and the button is stretched
- //to 100% of the available width. As a workaround, it looks like we can
- //wrap the button div in another div with the display style set to "table".
- var tableDiv = createElement('div', outerDiv);
- if (outerDiv.style.display==="inline-block"){
- tableDiv.style.display = "table";
- if (config.width) tableDiv.style.width = outerDiv.style.width;
- }
- tableDiv.style.height = "100%";
-
-
- //Create main div used to represent the button
- mainDiv = createElement('div', tableDiv, config.style.button);
- mainDiv.setAttribute("desc", "button");
- addEventHandlers(mainDiv);
-
-
-
- var table = createTable(mainDiv);
- table.style.fontFamily = "inherit";
- table.style.textAlign = "inherit";
- table.style.color = "inherit";
- var tr = table.addRow();
- var td;
-
-
- //Add icon (or label)
- td = tr.addColumn();
- if (iconAlignment==="left"){
- icon = createElement("div", td, config.style.icon);
- }
- else{
- arrow = createElement("div", td, config.style.arrow);
- }
-
-
- //Add label
- td = tr.addColumn({width: "100%"});
- label = createElement("div", td);
- setStyle(label, "label");
- if (config.label) label.innerHTML = config.label;
-
-
- //Add arrow (or icon)
- td = tr.addColumn();
- if (iconAlignment==="left"){
- arrow = createElement("div", td, config.style.arrow);
- }
- else{
- icon = createElement("div", td, config.style.icon);
- }
-
-
-
- //Create menu panel as needed
- if (config.menu===true){
- config.toggle = true;
- menu = createElement('div', outerDiv, config.style.menu);
- menu.setAttribute("desc", "menu");
- menu.style.position = "absolute";
- menu.style.visibility = "hidden";
-
-
- var hideMenu = function(e){
- if (!mainDiv.contains(e.target)){
- menu.style.visibility = "hidden";
- me.deselect();
- }
- };
-
- //Hide menu if the client clicks outside of the menu
- window.addEventListener('click', hideMenu);
-
-
- //Create logic to process touch events
- var touchStartTime, touchEndTime;
- var x1, x2, y1, y2;
-
- window.addEventListener('touchstart', function(e){
- x1 = e.changedTouches[0].pageX;
- y1 = e.changedTouches[0].pageY;
- touchStartTime = new Date().getTime();
- touchEndTime = null;
- });
-
- window.addEventListener('touchend', function(e){
-
- touchEndTime= new Date().getTime();
- x2 = e.changedTouches[0].pageX;
- y2 = e.changedTouches[0].pageY;
-
- var distance = Math.sqrt( (x2-=x1)*x2 + (y2-=y1)*y2 );
- if (distance<0) distance = -distance;
- var duration = touchEndTime - touchStartTime;
-
- if ((duration <= 500 && distance <= 10) || //Quick tap
- (duration > 500 && distance <= 10)) { //Long press
- hideMenu(e);
- }
- });
-
- }
-
-
- //Set button state
- if (config.disabled===true) me.disable();
- if (config.selected===true) me.select();
-
-
- //Add public show/hide methods
- addShowHide(me);
- };
-
-
- //**************************************************************************
- //** addEventHandlers
- //**************************************************************************
- var addEventHandlers = function(div){
-
- //Disable text selection
- div.unselectable="on";
- div.onselectstart=function(){return false;};
-
-
- //Create onclick function
- var onclick = function(e){
- e.stopPropagation();
- if (config.sound!=null) config.sound.play();
-
-
- if (config.toggle===true){
- if (menu){
-
- if (isTouch){
- me.toggle();
- }
- else{
- //Do nothing - button is toggled on mouse down...
- }
- }
- else{
- me.toggle();
- }
- }
- else{
- setStyle(mainDiv,"button");
- setStyle(icon,"icon");
- setStyle(arrow,"arrow");
- }
-
-
- me.onClick();
- };
-
-
- //Create logic to process touch events
- var touchStartTime;
- var touchEndTime;
- var x1, x2, y1, y2;
- var isTouch = false;
-
- div.ontouchstart = function(e) {
- isTouch = true;
-
- e.preventDefault();
- x1 = e.changedTouches[0].pageX;
- y1 = e.changedTouches[0].pageY;
- touchStartTime = new Date().getTime();
- touchEndTime = null;
-
- if (div.selected!==true){
- addStyle(div, "hover");
- addStyle(icon, "iconHover");
- addStyle(arrow, "arrowHover");
- }
- };
-
- div.ontouchend = function(e) {
-
- touchEndTime= new Date().getTime();
- x2 = e.changedTouches[0].pageX;
- y2 = e.changedTouches[0].pageY;
-
- var distance = Math.sqrt( (x2-=x1)*x2 + (y2-=y1)*y2 );
- if (distance<0) distance = -distance;
- var duration = touchEndTime - touchStartTime;
-
- if ((duration <= 500 && distance <= 10) || //Quick tap
- (duration > 500 && distance <= 10)) { //Long press
- onclick(e);
- }
- else{
- setStyle(div, "button");
- setStyle(icon, "icon");
- setStyle(arrow, "arrow");
- }
- };
-
-
-
- //Logic to process mouse events
- if (!isTouch){
- div.onmousedown=function(){
-
- addStyle(div, "select");
- addStyle(icon, "iconSelect");
- addStyle(arrow, "arrowSelect");
-
- if (menu){
- me.toggle();
- //TODO: Add mouseup events to buttons in the menu
- }
-
- //return false;
- };
- div.onclick = function(e){
- onclick(e);
- };
- div.onmouseover = function(){
-
- if (div.selected!==true){
- addStyle(div, "hover");
- addStyle(icon, "iconHover");
- addStyle(arrow, "arrowHover");
- }
-
- };
- div.onmouseout = function(){
-
- if (div.selected!==true){
- setStyle(div, "button");
- setStyle(icon, "icon");
- setStyle(arrow, "arrow");
- }
-
- };
- }
- };
-
-
- //**************************************************************************
- //** click
- //**************************************************************************
- /** Used to click the button and fire onClick event
- */
- this.click = function(){
- mainDiv.click();
- };
-
-
- //**************************************************************************
- //** onClick
- //**************************************************************************
- /** Called whenever the button is clicked.
- */
- this.onClick = function(){};
-
-
- //**************************************************************************
- //** getText
- //**************************************************************************
- /** Returns the button label. Same as getLabel().
- */
- this.getText = function(){
- return me.getLabel();
- };
-
-
- //**************************************************************************
- //** getLabel
- //**************************************************************************
- /** Returns the button label.
- */
- this.getLabel = function(){
- return label.innerText;
- };
-
-
- //**************************************************************************
- //** setLabel
- //**************************************************************************
- /** Used to update the button label.
- */
- this.setLabel = function(str){
- label.innerText = str+"";
- };
-
-
- //**************************************************************************
- //** enable
- //**************************************************************************
- /** Used to enable the button.
- */
- this.enable = function(){
- var outerDiv = me.el;
- outerDiv.style.opacity = "";
- if (mask) mask.style.visibility = "hidden";
- };
-
-
- //**************************************************************************
- //** disable
- //**************************************************************************
- /** Used to disable the button.
- */
- this.disable = function(){
-
- var outerDiv = me.el;
- outerDiv.style.opacity = "0.5";
-
- if (mask){
- mask.style.visibility = "visible";
- }
- else{
- mask = createElement('div',{
- position: "absolute",
- zIndex: "1",
- width: "100%",
- height: "100%"
- });
- mask.setAttribute("desc", "mask");
- outerDiv.insertBefore(mask, outerDiv.firstChild);
- }
- };
-
-
- //**************************************************************************
- //** isEnabled
- //**************************************************************************
- /** Returns true if the button is enabled (i.e. not disabled).
- */
- this.isEnabled = function(){
- return !me.isDisabled();
- };
-
-
- //**************************************************************************
- //** isDisabled
- //**************************************************************************
- /** Returns true if the button is disabled.
- */
- this.isDisabled = function(){
- if (mask){
- if (mask.style.visibility !== "hidden") return true;
- }
- return false;
- };
-
-
- //**************************************************************************
- //** select
- //**************************************************************************
- /** Used to update the "selected" state of the button.
- */
- this.select = function(){
- if (mainDiv.selected===true) return;
- mainDiv.selected = true;
- setStyle(mainDiv,"button");
- setStyle(icon,"icon");
- setStyle(arrow,"arrow");
- addStyle(mainDiv,"select");
- addStyle(icon,"iconSelect");
- addStyle(arrow,"arrowSelect");
- };
-
-
- //**************************************************************************
- //** deselect
- //**************************************************************************
- /** Used to update the "selected" state of the button.
- */
- this.deselect = function(){
- if (mainDiv.selected===true){
- mainDiv.selected = false;
- setStyle(mainDiv,"button");
- setStyle(icon,"icon");
- setStyle(arrow,"arrow");
- }
- };
-
-
- //**************************************************************************
- //** isSelected
- //**************************************************************************
- /** Returns true if the button is selected (e.g. depressed)
- */
- this.isSelected = function(){
- return (mainDiv.selected===true);
- };
-
-
- //**************************************************************************
- //** toggle
- //**************************************************************************
- /** Used to toggle the button's selection state.
- */
- this.toggle = function(){
- if (config.toggle===true){
- if (mainDiv.selected===true){
- me.deselect();
- if (menu) menu.style.visibility = "hidden";
- }
- else{
- me.select();
- if (menu) menu.style.visibility = "visible";
- }
- }
- };
-
-
- //**************************************************************************
- //** getMenuPanel
- //**************************************************************************
- /** Returns the DOM element associated with the menu panel. Typically, this
- * is used to render menu options (i.e. buttons).
- */
- this.getMenuPanel = function(){
- return menu;
- };
-
-
- //**************************************************************************
- //** Utils
- //**************************************************************************
- var merge = javaxt.dhtml.utils.merge;
- var createTable = javaxt.dhtml.utils.createTable;
- var createElement = javaxt.dhtml.utils.createElement;
- var addShowHide = javaxt.dhtml.utils.addShowHide;
- var setStyle = function(el, style){
- javaxt.dhtml.utils.setStyle(el, config.style[style]);
- };
- var addStyle = function(el, style){
- javaxt.dhtml.utils.addStyle(el, config.style[style]);
- };
-
-
- init();
+if(!javaxt) var javaxt={};
+if(!javaxt.dhtml) javaxt.dhtml={};
+
+//******************************************************************************
+//** Button Class
+//******************************************************************************
+/**
+ * Custom button control. The button has 3 parts: icon, label, and an arrow.
+ *
+ ******************************************************************************/
+
+javaxt.dhtml.Button = function(parent, config) {
+ this.className = "javaxt.dhtml.Button";
+
+ var me = this;
+ var defaultConfig = {
+
+ /** Text label for the button.
+ */
+ label: null,
+
+
+ /** If true, the button will initially appear in a selected state and the
+ * isSelected() method will return true. Default is false.
+ */
+ selected: false,
+
+
+ /** If true, the button will initially appear disabled and the
+ * isDisabled() method will return true. Default is false.
+ */
+ disabled: false,
+
+
+ /** If true, the button will be rendered as a toggle button. Default is
+ * false, unless the button has a menu.
+ */
+ toggle: false,
+
+
+ /** If true, will create a drop-down menu for the button. Components such
+ * as buttons or custom DOM elements can be added directly to the menu.
+ * See getMenuPanel() for more information.
+ */
+ menu: false,
+
+
+ /** Sets the position of the menu panel. Options are "bottom" or "right".
+ */
+ menuAlign: "bottom",
+
+
+
+ /** Used to set the "display" style attribute for the outer DOM element.
+ * This is not commonly ued. Default is "inline-block".
+ */
+ display: "inline-block",
+
+
+ /** Used to set the width of the button. This property is optional.
+ */
+ width: null,
+
+
+ /** Used to set the height of the button. This property is optional.
+ */
+ height: null,
+
+
+ /** Used to set the icon position relative to the button label. Options
+ * are "left" or "right". Note that the icon style is set in the style
+ * config. The icon style should not be used to control whether the
+ * icon appears to the left or right of the label. Use this config
+ * instead.
+ */
+ iconAlign: "left",
+
+
+ /** Used to set spacing between the button icon and the button label.
+ * Default is "5px".
+ */
+ iconPadding: "5px",
+
+
+ /** Style for individual elements within the component. Note that you can
+ * provide CSS class names instead of individual style definitions.
+ */
+ style:{
+
+ button: {
+ border: "1px solid #cccccc",
+ borderRadius: "3px",
+ background: "#F6F6F6",
+ cursor: "pointer",
+ padding: "3px 7px",
+ margin: "0px",
+ color: "#2b2b2b"
+ },
+
+ label: {
+ fontFamily: "helvetica,arial,verdana,sans-serif",
+ fontSize: "14px",
+ whiteSpace: "nowrap"
+ },
+
+ icon: {
+
+ },
+
+ arrow: {
+
+ },
+
+ select: {
+ background: "#007FFF",
+ border: "1px solid #003EFF",
+ color: "#FFFFFF"
+ },
+
+ hover: {
+ background: "#ededed"
+ },
+
+
+ menu: {
+ border: "1px solid #cccccc",
+ background: "#F6F6F6",
+ cursor: "pointer",
+ padding: "3px 3px",
+ zIndex: "1"
+ }
+
+ },
+
+
+ /** Sound to play when the button is clicked
+ */
+ sound: null
+ };
+
+ var mainDiv;
+ var mask, menu;
+ var icon, label, arrow;
+
+
+ //**************************************************************************
+ //** Constructor
+ //**************************************************************************
+ var init = function(){
+
+ if (typeof parent === "string"){
+ parent = document.getElementById(parent);
+ }
+ if (!parent) return;
+
+
+ //Clone the config so we don't modify the original config object
+ var clone = {};
+ merge(clone, config);
+
+
+ //Merge clone with default config
+ merge(clone, defaultConfig);
+ config = clone;
+
+
+ //Get icon alignment
+ var iconAlignment = config.style.iconAlign; //legacy
+ if (!iconAlignment) iconAlignment = config.iconAlign; //preferred
+ if (iconAlignment!=="right") iconAlignment = "left";
+ config.iconAlign = iconAlignment;
+
+
+ //Get menu alignment
+ var menuAlignment = config.style.menuAlignment; //legacy
+ if (!menuAlignment) menuAlignment = config.menuAlign; //preferred
+ if (menuAlignment!=="right") menuAlignment = "bottom";
+
+
+ //Update arrow style as needed
+ if (config.menu===true){
+ var arrowDefined = false;
+ if (typeof config.style.arrow === "string"){
+ arrowDefined = true;
+ }
+ else{
+ for (var key in config.style.arrow){
+ if (config.style.arrow.hasOwnProperty(key)){
+ arrowDefined = true;
+ break;
+ }
+ }
+ }
+
+ if (!arrowDefined){
+ if (menuAlignment==="bottom"){
+ config.style.arrow = {
+ width: 0,
+ height: 0,
+ borderLeft: "5px solid transparent",
+ borderRight: "5px solid transparent",
+ borderTop: "5px solid #575757",
+ marginLeft: "10px"
+ };
+ config.style.arrowSelect = {
+ borderTop: "5px solid #FFFFFF"
+ };
+ }
+ }
+ }
+
+
+
+ //Create outer div used to hold the button, mask, and menu
+ var outerDiv = createElement('div', parent);
+ outerDiv.className = "javaxt-button";
+
+
+ //Only set display if there's a mismatch (cleaner dom)
+ var cs = window.getComputedStyle(outerDiv);
+ if (cs.display!=config.display){
+ outerDiv.style.display = config.display;
+ }
+
+ //Simlarly, only set position as needed
+ if (cs.position!="relative"){
+ outerDiv.style.position = "relative";
+ }
+
+ //Set width/height as needed
+ if (config.width){
+ if (typeof config.width === "string"){
+ outerDiv.style.width = config.width;
+ }
+ else{
+ outerDiv.style.width = config.width + "px";
+ }
+ }
+ if (config.height){
+ if (typeof config.height === "string"){
+ outerDiv.style.height = config.height;
+ }
+ else{
+ outerDiv.style.height = config.height + "px";
+ }
+ }
+ if (config.hidden===true){ //legacy config...
+ outerDiv.style.visibility = 'hidden';
+ outerDiv.style.display = 'none';
+ }
+ me.el = outerDiv;
+
+
+
+
+ //The button is implemented using a simple HTML table with 3 columns. The
+ //left and right columns are for icons and the center column is for the
+ //button label. The width of the center column is set to 100% and the
+ //width of the left and right columns are defined by the "icon" and
+ //"arrow" styles. Unfortunately, some browsers seem to have issues
+ //rendering the table correctly inside of a div when the outerDiv's
+ //display style set to "inline-block". For example, Mobile Safari will
+ //completely ignore the "inline-block" style and stretch the button to
+ //100% of the available width. In Chrome, if the left or right columns has
+ //a width, the "inline-block" style is ignored and the button is stretched
+ //to 100% of the available width. As a workaround, it looks like we can
+ //wrap the button div in another div with the display style set to "table".
+ var tableDiv = createElement('div', outerDiv);
+ if (outerDiv.style.display==="inline-block" || cs.display==="inline-block"){
+ tableDiv.style.display = "table";
+ if (config.width) tableDiv.style.width = outerDiv.style.width;
+ }
+ tableDiv.style.height = "100%";
+
+
+ //Create main div used to represent the button
+ mainDiv = createElement('div', tableDiv, config.style.button);
+ mainDiv.setAttribute("desc", "button");
+ addEventHandlers(mainDiv);
+
+
+
+ var table = createTable(mainDiv);
+ table.style.fontFamily = "inherit";
+ table.style.textAlign = "inherit";
+ table.style.color = "inherit";
+ var tr = table.addRow();
+ var td;
+
+
+ //Add icon (or label)
+ td = tr.addColumn();
+ if (iconAlignment==="left"){
+ icon = createElement("div", td, config.style.icon);
+ }
+ else{
+ arrow = createElement("div", td, config.style.arrow);
+ }
+
+
+ //Add label
+ td = tr.addColumn({width: "100%"});
+ label = createElement("div", td);
+ setStyle(label, "label");
+
+
+
+ //Add arrow (or icon)
+ td = tr.addColumn();
+ if (iconAlignment==="left"){
+ arrow = createElement("div", td, config.style.arrow);
+ }
+ else{
+ icon = createElement("div", td, config.style.icon);
+ }
+
+
+
+ //Add show/hide to components
+ addShowHide(label);
+ addShowHide(icon);
+ addShowHide(arrow);
+
+
+
+ //Hide icon as needed
+ var hideIcon = true;
+ if (typeof config.style.icon === "string"){
+ hideIcon = false;
+ }
+ else{
+ for (var key in config.style.icon){
+ if (config.style.icon.hasOwnProperty(key)){
+ hideIcon = false;
+ break;
+ }
+ }
+ }
+ if (hideIcon) icon.hide();
+
+
+
+
+ //Create menu panel as needed
+ if (config.menu===true){
+ config.toggle = true;
+ menu = createElement('div', outerDiv, config.style.menu);
+ menu.setAttribute("desc", "menu");
+ menu.style.position = "absolute";
+ menu.style.visibility = "hidden";
+
+
+ var hideMenu = function(e){
+ if (!mainDiv.contains(e.target)){
+ menu.style.visibility = "hidden";
+ me.deselect();
+ }
+ };
+
+ //Hide menu if the client clicks outside of the menu
+ window.addEventListener('click', hideMenu);
+
+
+ //Create logic to process touch events
+ var touchStartTime, touchEndTime;
+ var x1, x2, y1, y2;
+
+ window.addEventListener('touchstart', function(e){
+ x1 = e.changedTouches[0].pageX;
+ y1 = e.changedTouches[0].pageY;
+ touchStartTime = new Date().getTime();
+ touchEndTime = null;
+ });
+
+ window.addEventListener('touchend', function(e){
+
+ touchEndTime= new Date().getTime();
+ x2 = e.changedTouches[0].pageX;
+ y2 = e.changedTouches[0].pageY;
+
+ var distance = Math.sqrt( (x2-=x1)*x2 + (y2-=y1)*y2 );
+ if (distance<0) distance = -distance;
+ var duration = touchEndTime - touchStartTime;
+
+ if ((duration <= 500 && distance <= 10) || //Quick tap
+ (duration > 500 && distance <= 10)) { //Long press
+ hideMenu(e);
+ }
+ });
+
+ }
+
+
+ //Set button label
+ me.setLabel(config.label);
+
+
+ //Set button state
+ if (config.disabled===true) me.disable();
+ if (config.selected===true) me.select();
+
+
+ //Add public show/hide methods
+ addShowHide(me);
+ };
+
+
+ //**************************************************************************
+ //** addEventHandlers
+ //**************************************************************************
+ var addEventHandlers = function(div){
+
+ //Disable text selection
+ div.unselectable="on";
+ div.onselectstart=function(){return false;};
+
+
+ //Create onclick function
+ var onclick = function(e){
+ e.stopPropagation();
+ if (config.sound!=null) config.sound.play();
+
+
+ if (config.toggle===true){
+ if (menu){
+
+ if (isTouch){
+ me.toggle();
+ }
+ else{
+ //Do nothing - button is toggled on mouse down...
+ }
+ }
+ else{
+ me.toggle();
+ }
+ }
+ else{
+ setDefaultStyle(mainDiv);
+ }
+
+
+ me.onClick();
+ };
+
+
+ //Create logic to process touch events
+ var touchStartTime;
+ var touchEndTime;
+ var x1, x2, y1, y2;
+ var isTouch = false;
+
+ div.ontouchstart = function(e) {
+ isTouch = true;
+
+ e.preventDefault();
+ x1 = e.changedTouches[0].pageX;
+ y1 = e.changedTouches[0].pageY;
+ touchStartTime = new Date().getTime();
+ touchEndTime = null;
+
+ if (div.selected!==true){
+ setHoverStyle(div);
+ }
+ };
+
+ div.ontouchend = function(e) {
+
+ touchEndTime= new Date().getTime();
+ x2 = e.changedTouches[0].pageX;
+ y2 = e.changedTouches[0].pageY;
+
+ var distance = Math.sqrt( (x2-=x1)*x2 + (y2-=y1)*y2 );
+ if (distance<0) distance = -distance;
+ var duration = touchEndTime - touchStartTime;
+
+ if ((duration <= 500 && distance <= 10) || //Quick tap
+ (duration > 500 && distance <= 10)) { //Long press
+ onclick(e);
+ }
+ else{
+ setDefaultStyle(div);
+ }
+ };
+
+
+
+ //Logic to process mouse events
+ if (!isTouch){
+ div.onmousedown=function(){
+
+ setSelectStyle(div);
+
+ if (menu){
+ me.toggle();
+ //TODO: Add mouseup events to buttons in the menu
+ }
+
+ //return false;
+ };
+ div.onclick = function(e){
+ onclick(e);
+ };
+ div.onmouseover = function(){
+ if (div.selected!==true){
+ setHoverStyle(div);
+ }
+ };
+ div.onmouseout = function(){
+ if (div.selected!==true){
+ setDefaultStyle(div);
+ }
+ };
+ }
+ };
+
+
+ //**************************************************************************
+ //** click
+ //**************************************************************************
+ /** Used to click the button and fire onClick event
+ */
+ this.click = function(){
+ mainDiv.click();
+ };
+
+
+ //**************************************************************************
+ //** onClick
+ //**************************************************************************
+ /** Called whenever the button is clicked.
+ */
+ this.onClick = function(){};
+
+
+ //**************************************************************************
+ //** getText
+ //**************************************************************************
+ /** Returns the button label. Same as getLabel().
+ */
+ this.getText = function(){
+ return me.getLabel();
+ };
+
+
+ //**************************************************************************
+ //** getLabel
+ //**************************************************************************
+ /** Returns the button label.
+ */
+ this.getLabel = function(){
+ return label.innerText;
+ };
+
+
+ //**************************************************************************
+ //** setLabel
+ //**************************************************************************
+ /** Used to update the button label.
+ */
+ this.setLabel = function(str){
+ if (typeof str === 'undefined' || str===null || str.length===0){
+ label.innerText = "";
+ label.hide();
+ }
+ else{
+ str = str+"";
+ label.innerText = str;
+ label.show();
+ addLabelPadding(str); //Explicitely pass the label (bug fix)
+ }
+ };
+
+
+ //**************************************************************************
+ //** enable
+ //**************************************************************************
+ /** Used to enable the button.
+ */
+ this.enable = function(){
+ var outerDiv = me.el;
+ outerDiv.style.opacity = "";
+ if (mask) mask.style.visibility = "hidden";
+ };
+
+
+ //**************************************************************************
+ //** disable
+ //**************************************************************************
+ /** Used to disable the button.
+ */
+ this.disable = function(){
+
+ var outerDiv = me.el;
+ outerDiv.style.opacity = "0.5";
+
+ if (mask){
+ mask.style.visibility = "visible";
+ }
+ else{
+ mask = createElement('div',{
+ position: "absolute",
+ zIndex: "1",
+ width: "100%",
+ height: "100%"
+ });
+ mask.setAttribute("desc", "mask");
+ outerDiv.insertBefore(mask, outerDiv.firstChild);
+ }
+ };
+
+
+ //**************************************************************************
+ //** isEnabled
+ //**************************************************************************
+ /** Returns true if the button is enabled (i.e. not disabled).
+ */
+ this.isEnabled = function(){
+ return !me.isDisabled();
+ };
+
+
+ //**************************************************************************
+ //** isDisabled
+ //**************************************************************************
+ /** Returns true if the button is disabled.
+ */
+ this.isDisabled = function(){
+ if (mask){
+ if (mask.style.visibility !== "hidden") return true;
+ }
+ return false;
+ };
+
+
+ //**************************************************************************
+ //** select
+ //**************************************************************************
+ /** Used to update the "selected" state of the button.
+ */
+ this.select = function(){
+ if (mainDiv.selected===true) return;
+ mainDiv.selected = true;
+ setDefaultStyle(mainDiv);
+ setSelectStyle(mainDiv);
+ };
+
+
+ //**************************************************************************
+ //** deselect
+ //**************************************************************************
+ /** Used to update the "selected" state of the button.
+ */
+ this.deselect = function(){
+ if (mainDiv.selected===true){
+ mainDiv.selected = false;
+ setDefaultStyle(mainDiv);
+ }
+ };
+
+
+ //**************************************************************************
+ //** isSelected
+ //**************************************************************************
+ /** Returns true if the button is selected (e.g. depressed)
+ */
+ this.isSelected = function(){
+ return (mainDiv.selected===true);
+ };
+
+
+ //**************************************************************************
+ //** toggle
+ //**************************************************************************
+ /** Used to toggle the button's selection state.
+ */
+ this.toggle = function(){
+ if (config.toggle===true){
+ if (mainDiv.selected===true){
+ me.deselect();
+ if (menu) menu.style.visibility = "hidden";
+ }
+ else{
+ me.select();
+ if (menu) menu.style.visibility = "visible";
+ }
+ }
+ };
+
+
+ //**************************************************************************
+ //** getMenuPanel
+ //**************************************************************************
+ /** Returns the DOM element associated with the menu panel. Typically, this
+ * is used to render menu options (i.e. buttons).
+ */
+ this.getMenuPanel = function(){
+ return menu;
+ };
+
+
+ //**************************************************************************
+ //** setDefaultStyle
+ //**************************************************************************
+ var setDefaultStyle = function(div){
+ setStyle(div, "button");
+ setStyle(icon, "icon");
+ setStyle(arrow, "arrow");
+ addLabelPadding();
+ };
+
+
+ //**************************************************************************
+ //** setHoverStyle
+ //**************************************************************************
+ var setHoverStyle = function(div){
+ addStyle(div, "hover");
+ addStyle(icon, "iconHover");
+ addStyle(arrow, "arrowHover");
+ addLabelPadding();
+ };
+
+
+ //**************************************************************************
+ //** setSelectStyle
+ //**************************************************************************
+ var setSelectStyle = function(div){
+ addStyle(div, "select");
+ addStyle(icon, "iconSelect");
+ addStyle(arrow, "arrowSelect");
+ addLabelPadding();
+ };
+
+
+ //**************************************************************************
+ //** addLabelPadding
+ //**************************************************************************
+ var addLabelPadding = function(){
+ if (icon.isVisible()){
+ var str = arguments.length>0 ? arguments[0] : me.getLabel();
+ if (!(typeof str === 'undefined' || str===null || str.length===0)){
+ if (config.iconAlign==="left"){
+ icon.style.marginRight = config.iconPadding;
+ }
+ else{
+ icon.style.marginLeft = config.iconPadding;
+ }
+ }
+ }
+ };
+
+
+ //**************************************************************************
+ //** Utils
+ //**************************************************************************
+ var merge = javaxt.dhtml.utils.merge;
+ var isEmpty = javaxt.dhtml.utils.isEmpty;
+ var createTable = javaxt.dhtml.utils.createTable;
+ var createElement = javaxt.dhtml.utils.createElement;
+ var addShowHide = javaxt.dhtml.utils.addShowHide;
+ var setStyle = function(el, style){
+
+ //Don't set empty style. Especially for hidden elements (e.g. icon)
+ var s = config.style[style];
+ if (isEmpty(s)) return;
+
+ javaxt.dhtml.utils.setStyle(el, s);
+ };
+ var addStyle = function(el, style){
+ javaxt.dhtml.utils.addStyle(el, config.style[style]);
+ };
+
+
+ init();
};
\ No newline at end of file
diff --git a/src/calendar/Calendar.css b/src/calendar/Calendar.css
deleted file mode 100644
index fce839c..0000000
--- a/src/calendar/Calendar.css
+++ /dev/null
@@ -1,276 +0,0 @@
-
-/**************************************************************************/
-/** Header
-/**************************************************************************/
-
-/* style for the header row */
-.javaxt-cal-header {
- border: 1px solid #99BBE8;
- background: none repeat scroll 0 0 #EEEEEE;
- height: 25px;
-
-}
-
-/* style for individual cells in the header */
-.javaxt-cal-header-col {
- border-left: 1px solid #99BBE8;
- border-right: 1px solid #99BBE8;
- text-align: center;
- font-family: tahoma,arial,verdana,sans-serif;
- font-size: 11px;
- padding-top: 5px;
-}
-
-
-
-/**************************************************************************/
-/** Multiday Event Header
-/**************************************************************************/
-
-/* style for the header row */
-.javaxt-cal-multiday-header {
- border-bottom: 1px solid #99BBE8;
- border-left: 1px solid #000000;
- border-right: 1px solid #000000;
- background-color: #FFFFFF;
-}
-
-/* style for individual cells in the header */
-.javaxt-cal-multiday-col {
- border-left: 1px solid #000000;
- border-right: 1px solid #000000;
- padding-top: 0px;
- vertical-align: top;
-}
-
-/* style for the left cell in the multiday header */
-.javaxt-cal-multiday-col-spacer {
-
-}
-
-
-/**************************************************************************/
-/** Body
-/**************************************************************************/
-
-/* style for the body row */
-.javaxt-cal-body {
- border-right: 1px solid #000000;
- border-bottom: 1px solid #000000;
- border-left: 1px solid #000000;
-}
-
-
-/**************************************************************************/
-/** Cells
-/**************************************************************************/
-
-/* Style for individual cells in the body. Note that the cell header and
- footer are set using seperate css classes
-*/
-.javaxt-cal-cell {
- border-right: 1px solid #000000;
- border-left: 1px solid #000000;
- vertical-align: top;
-}
-
-/* Style for headers inside individual cells (e.g. numbers inside the month view)*/
-.javaxt-cal-cell-header {
- height: 15px;
- padding:0 2px 0 0;
- text-align: right;
- font-family: tahoma,arial,verdana,sans-serif;
- font-size: 11px;
-}
-
-.javaxt-cal-cell-footer {
- border-bottom: 1px solid #000000;
-}
-
-
-/* background color for days before and after current month */
-.javaxt-cal-cell-prev-month, .javaxt-cal-cell-next-month {
- background: none repeat scroll 0 0 #EFF9FC;
- border-right: 1px solid #000000;
- border-left: 1px solid #000000;
-}
-
-
-
-/**************************************************************************/
-/** Hours
-/**************************************************************************/
-/** Style for horizonal lines within the cells used to deliniate hours and
- * half hours. Note that the heights are very important. Make sure the
- * height of the half-hour is exactly 1/2 the height of an hour.
- */
-
-.javaxt-cal-hour {
- height: 55px; /* 56px-1px for border */
- border-top: 1px solid #99BBE8;
-}
-
-.javaxt-cal-half-hour {
- height: 27px; /* 28px-1px for border */
- border-top: 1px solid #99BBE8;
-}
-
-.javaxt-cal-half-hour-sep {
- border-top: 1px solid #D7E8FF;
-}
-
-.javaxt-cal-hour-last {
- border-bottom: 1px solid #99BBE8;
-}
-
-
-/**************************************************************************/
-/** Hour Labels
-/**************************************************************************/
-/** Style for the hour labels that appear in the left column of the day and
- * week views.
- */
-
-.javaxt-cal-label-hour {
- font-family:"Times New Roman", Times, serif;
- font-size: 18pt;
- vertical-align: top;
-}
-
-.javaxt-cal-label-meridian {
- font-family: tahoma,arial,verdana,sans-serif;
- font-size: 11px;
- padding: 0px 0px 0px 3px;
- vertical-align: top;
-}
-
-
-/**************************************************************************/
-/** Current Time Indicator
-/**************************************************************************/
-/** Style for the horizonal line used to indicate the current time in the
- * day and week views.
- */
-
-.javaxt-cal-current-time-indicator {
- border-top: 1px solid #FFA1A1;
-}
-
-
-/**************************************************************************/
-/** Events
-/**************************************************************************/
-/** Style for individual events. */
-
-.javaxt-cal-event {
-
- font-family: tahoma,arial,verdana,sans-serif;
- font-size: 11px;
- padding-left: 4px; /* Padding inside the event div */
- white-space: nowrap;
-
- cursor: pointer;
-
-
- border: 1px solid #803D5E;
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
-
- background-color: #E2C0D0;
- background: linear-gradient(#F1E2E9, #E2C0D0);
- background: -ms-linear-gradient(#F1E2E9, #E2C0D0);
- background: -webkit-linear-gradient(#F1E2E9, #E2C0D0);
- background: -moz-linear-gradient(center top, #F1E2E9, #E2C0D0);
-}
-
-
-/* Left border for multi-day events that extend beyond the first cell in a
- row or view
-*/
-.javaxt-cal-event-continue-left {
- -moz-border-radius: 0px;
- -webkit-border-radius: 0px 3px 3px 0px;
- border-radius: 0px 3px 3px 0px;
- border-left: 0px;
-}
-
-
-/* Right border for multi-day events that extend beyond the last cell in a
- row or view
-*/
-.javaxt-cal-event-continue-right {
- -moz-border-radius: 0px;
- -webkit-border-radius: 3px 0px 0px 3px;
- border-radius: 3px 0px 0px 3px;
- border-right: 0px;
-}
-
-
-/* Left and Right borders for multi-day events that extend beyond the first and
- last cell in a row/view
-*/
-.javaxt-cal-event-continue-left.javaxt-cal-event-continue-right {
- -webkit-border-radius: 0px;
- -moz-border-radius: 0px;
- border-radius: 0px;
-}
-
-
-/* Style for events as they are being dragged */
-.javaxt-cal-event-drag {
- cursor: move;
- box-shadow: 0 12px 14px 0 rgba(0, 0, 0, 0.2), 0 13px 20px 0 rgba(0, 0, 0, 0.2);
-}
-
-/**************************************************************************/
-/** Alternative Colors for Events
-/**************************************************************************/
-
-.javaxt-cal-event-blue {
- border: 1px solid #6986BE;
- background-color: #DBE1EF;
-
- background: linear-gradient(#FFFFFF, #DBE1EF);
- background: -ms-linear-gradient(#FFFFFF, #DBE1EF);
- background: -webkit-linear-gradient(#FFFFFF, #DBE1EF);
- background: -moz-linear-gradient(center top, #FFFFFF, #DBE1EF);
-}
-
-.javaxt-cal-event-red {
- border: 1px solid #A81D24;
- background-color: #E55F66;
- /*background-image: -moz-linear-gradient(center top , #F2B6B6, #E55F66);*/
-
- background: linear-gradient(#FFFFFF, #E55F66);
- background: -ms-linear-gradient(#FFFFFF, #E55F66);
- background: -webkit-linear-gradient(#FFFFFF, #E55F66);
- background: -moz-linear-gradient(center top, #FFFFFF, #E55F66);
-}
-
-
-.javaxt-cal-event-yellow {
- border: 1px solid #9D7925;
- background-color: #FFFF83;
- /*background-image: -moz-linear-gradient(center top , #FFFDD6, #FFFF83);*/
-
- background: linear-gradient(#FFFFFF, #FFFF83);
- background: -ms-linear-gradient(#FFFFFF, #FFFF83);
- background: -webkit-linear-gradient(#FFFFFF, #FFFF83);
- background: -moz-linear-gradient(center top, #FFFFFF, #FFFF83);
-}
-
-
-
-/**************************************************************************/
-/** Misc
-/**************************************************************************/
-
-.javaxt-noselect {
- -webkit-user-select: none;
- -moz-user-select: none;
- -o-user-select: none;
- -ms-user-select: none;
- -khtml-user-select: none;
- user-select: none;
-}
\ No newline at end of file
diff --git a/src/calendar/Calendar.js b/src/calendar/Calendar.js
index 8482c27..4582a74 100644
--- a/src/calendar/Calendar.js
+++ b/src/calendar/Calendar.js
@@ -1,292 +1,445 @@
-if(!javaxt) var javaxt={};
-if(!javaxt.dhtml) javaxt.dhtml={};
-
-//******************************************************************************
-//** Calendar Class
-//*****************************************************************************/
-/**
- * Used to render events for a given month/week/day
- *
- ******************************************************************************/
-
-javaxt.dhtml.Calendar = function(parent, config) {
-
- this.className = "javaxt.dhtml.Calendar";
-
- var me = this;
- var view = null;
- var views = {};
- var currView;
-
-
- var rendered = false;
- var deferredEvents = [];
- var _listeners = {};
-
-
- var supportedViews = {
- day: javaxt.dhtml.calendar.Day,
- week: javaxt.dhtml.calendar.Week,
- month: javaxt.dhtml.calendar.Month
- };
-
-
- //**************************************************************************
- //** Constructor
- //**************************************************************************
- /** Creates a new instance of this class. */
-
- var init = function(){
-
- //Clone the config so we don't modify the original config object
- var clone = {};
- merge(clone, config);
- config = clone;
-
-
- //Set store
- if (config.eventStore==null) config.eventStore = new javaxt.dhtml.calendar.EventStore();
-
-
-
- //Replace listeners with local callbacks so that the events appear to
- //be fired from this class instead of the views
- var listeners = config.listeners;
- var beforerender = null;
- var afterrender = null;
- if (listeners!=null){
-
- for (var listenerName in listeners) {
- if (listeners.hasOwnProperty(listenerName)) {
- var listener = listeners[listenerName];
-
-
- if (listenerName=='beforerender'){
- beforerender = listener;
- delete listeners[listenerName];
- }
- else if (listenerName=='afterrender'){
- afterrender = listener;
- delete listeners[listenerName];
- }
- else{
- _listeners[listenerName] = listener;
- (function(listenerName) {
-
- listeners[listenerName] = function(){
- //console.log("** fire " + listenerName + "? " + rendered);
-
-
- var args = [];
- for (var i=0; i -1;
- var isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 ||
- window.navigator.userAgent.indexOf("Trident/") > -1;
-
-
-
- //**************************************************************************
- //** Constructor
- //**************************************************************************
- /** Creates a new instance of the calendar control. */
-
- var init = function(){
-
- //Call super
- new javaxt.dhtml.calendar.View(me, config);
-
- //Set store
- store = config.eventStore==null ? new javaxt.dhtml.calendar.EventStore() : config.eventStore;
-
- //Set number of days to render
- if (config.days!=null) days = config.days;
-
- //Set event size, padding, and spacing
- function isNumeric(n){ return !isNaN(parseFloat(n)) && isFinite(n); }
- if (isNumeric(config.eventHeight)) eventHeight = parseInt(config.eventHeight);
- if (isNumeric(config.eventPadding)) eventPadding = parseInt(config.eventPadding);
-
-
- //Specify function used to get current date
- getCurrentDate = config.getCurrentDate==null ?
- getCurrentDate = function(){return new Date();} : config.getCurrentDate;
-
-
-
- //Configure renderers
- if (config.renderers){
- for (var rendererName in config.renderers) {
- if (config.renderers.hasOwnProperty(rendererName)) {
- if (me[rendererName]){
-
- //Override the default renderer
- (function(rendererName) {
- me[rendererName] = function(){
- var renderer = config.renderers[rendererName];
- return renderer.apply(me, arguments);
- };
-
- })(rendererName);
- }
- }
- }
- }
-
-
- //Call the beforerender callback
- rendered = false;
- var listener = me.getListener('beforerender');
- if (listener!=null) listener.callback.apply(listener.scope, [me]);
-
- //Set date and render the calendar
- me.setDate(config.date);
-
- //Call the afterrender callback
- listener = me.getListener('afterrender');
- if (listener!=null) listener.callback.apply(listener.scope, [me]);
- rendered = true;
-
- //Call the update callback
- listener = me.getListener('update');
- if (listener!=null) listener.callback.apply(listener.scope, [me]);
- };
-
-
- //**************************************************************************
- //** hasHours
- //**************************************************************************
- this.hasHours = function(){
- return true;
- };
-
-
- //**************************************************************************
- //** show
- //**************************************************************************
- this.show = function(){
- parent.appendChild(el);
- };
-
-
- //**************************************************************************
- //** hide
- //**************************************************************************
- this.hide = function(){
- parent.removeChild(el);
- };
-
-
- //**************************************************************************
- //** showFooter
- //**************************************************************************
- this.showFooter = function(){
- footerRow.style.display = '';
- };
-
-
- //**************************************************************************
- //** hideFooter
- //**************************************************************************
- this.hideFooter = function(){
- footerRow.style.display = "none";
- };
-
-
- //**************************************************************************
- //** enableTouch
- //**************************************************************************
- this.enableTouch = function(){
- scrollable = true;
- };
-
-
- //**************************************************************************
- //** disableTouch
- //**************************************************************************
- this.disableTouch = function(){
- scrollable = false;
- };
-
-
- //**************************************************************************
- //** renderTable
- //**************************************************************************
- /** Used to render a new table for the current date. */
-
- var renderTable = function(){
-
-
- //Remove any previously rendered table
- if (el!=null){
- for (var i=0; i1) text = text.substring(0,3);
- //else text+= ", " + javaxt.dhtml.calendar.Utils.monthNames[d.getMonth()] + " " + d.getDate();
- innerDiv.innerHTML = text;
-
-
- outerDiv.appendChild(innerDiv);
- return outerDiv;
- };
-
-
-
- this.createColumnFooter = function(i){
- return document.createElement('div');
- };
-
-
-
- //**************************************************************************
- //** createHourLabel
- //**************************************************************************
- /** Returns a div used to indicate the hour of day (e.g. 12pm). This method
- * can be safely overridden to generate custom labels for hours.
- */
- this.createHourLabel = function(hour){
- var div = document.createElement('div');
- div.style.float = "right";
- div.style.padding = "0px 7px 0px 7px";
-
-
- //Update hour and set meridian
- var meridian = 'AM';
- if (hour==0) hour = 12;
- else if (hour==12) meridian = 'PM';
- else if (hour>12){
- hour = (hour-12);
- meridian = 'PM';
- }
-
- //Create table to render the hour and meridian
- var hdr = document.createElement('table');
- hdr.cellSpacing = 0;
- hdr.cellPadding = 0;
- div.appendChild(hdr);
- var t = document.createElement('tbody');
- hdr.appendChild(t);
- var tr = document.createElement('tr');
- t.appendChild(tr);
- var td = document.createElement('td');
- td.className = "javaxt-cal-label-hour";
- td.innerHTML = hour;
- tr.appendChild(td);
- td = document.createElement('td');
- td.className = "javaxt-cal-label-meridian";
- td.innerHTML = meridian;
- tr.appendChild(td);
-
- return div;
- };
-
-
- //**************************************************************************
- //** updateCurrTime
- //**************************************************************************
- /** Used to update the position of the current time indicator.
- */
- var updateCurrTime = function(){
-
- var date = getCurrentDate();
- var cellID = (date.getMonth()+1) + "-" + date.getDate() + "-" + date.getFullYear();
- var cell = cells[cellID];
- if (cell){
-
- var hours = date.getHours();
- var h1 = getVerticalOffset(hours, cell);
- var h2 = getVerticalOffset(hours+1, cell);
-
- var pixelsPerMinute = (h2-h1)/60;
- var h = h1+(date.getMinutes()*pixelsPerMinute);
-
- currTimeDiv.style.display = '';
- currTimeDiv.style.top = h + "px";
- }
- else{
- currTimeDiv.style.display = "none";
- }
- };
-
-
- //**************************************************************************
- //** scrollTo
- //**************************************************************************
- /** Used to scroll to a specific time of day.
- * @param hour Time of day, specified as a decimal (e.g. 6.5 for 6:30 AM)
- */
- this.scrollTo = function(hour){
- var cellID = (date.getMonth()+1) + "-" + date.getDate() + "-" + date.getFullYear();
- var cell = cells[cellID];
- var offset = getVerticalOffset(hour, cell);
- bodyDiv.scrollTop = offset+1; //+1 for border
- };
-
-
- //**************************************************************************
- //** getScrollDiv
- //**************************************************************************
- this.getScrollDiv = function(){
- return bodyDiv;
- };
-
-
- //**************************************************************************
- //** getVerticalOffset
- //**************************************************************************
- /** Returns the relative, vertical offset of a row in a cell.
- */
- var getVerticalOffset = function(hour, cell){
-
- //Compute row heights and cache the values for subsequent use. Caching
- //the row heights improves load times by approx 500ms in IE and 1000ms
- //in FF. Caching assumes that row heights are the same accross all cells
- //and that the row heights do not change.
- if (rowHeights.length==0){
- var tbody = cell.childNodes[0].childNodes[0];
- var rows = tbody.childNodes;
- for (var i=0; i=hour){
- return (offset-rowHeight);
- }
- }
- return 0;
- };
-
-
- //**************************************************************************
- //** getEventStore
- //**************************************************************************
- this.getEventStore = function(){
- return store;
- };
-
-
- //**************************************************************************
- //** addEvent
- //**************************************************************************
- this.addEvent = function(event){
- addEvent(event);
- };
-
-
- //**************************************************************************
- //** addEvent
- //**************************************************************************
- /** Private method used to add an event to the view.
- * @param deferUpdates Option to postpone or defer updating the event
- * width and position when there are overlapping events. This option is
- * provided for optimization.
- */
- var addEvent = function(event, deferUpdates){
-
- log("Adding " + event.getSubject() + "...");
- var numDays = event.numDays();
-
-
- //Check if we've already rendered the given event
- if (numDays>=1){
- for (var i=1; i0){
- var div = td.childNodes[0];
- if (div.event.equals(event)){
- return;
- }
- }
- }
- }
-
- }
- else{
- var divs = getDivs(event.getStartDate());
- for (var i=0; i=1){
- addMultiDayEvent(event);
- }
- else{
- addSingleDayEvent(event, deferUpdates);
- }
-
-
- //Update the event store
- store.add(event);
- };
-
-
- //**************************************************************************
- //** addEvents
- //**************************************************************************
- /** Used to add multiple events to the view. This method is recommended for
- * bulk loading and is significantly faster than calling addEvent multiple
- * times.
- */
- this.addEvents = function(events){
-
-
- //Add events but defer updating position and widths of overlapping events
- for (var i=0; i0){
-
- //Compute number of columns
- var numColumns = 2;
- if (overlappingEvents.length>1){
-
- //The following logic is somewhat flawed and may yeild
- //a column count slightly higher than expected...
- for (var j=0; j0){
- var _numColumns = 1 + 1 + numIntersects;
- if (_numColumns>numColumns) numColumns = _numColumns;
- }
-
- }
- }
-
-
- var width = (Math.floor((1/(numColumns))*100));
- log("Update " + event.getSubject() + " width to " + width + "%");
- var outerDiv = getDiv(event);
- outerDiv.style.width = width + "%";
-
-
- for (var j=0; j0){
-
- for (var j=0; j1){
-
- var left = parseInt(outerDiv.style.left);
- var colIndex = Math.floor(left/width)+1;
-
-
- log(event.getSubject() + " is in column " + colIndex + "/" + numColumns);
-
- if (colIndex===(numColumns-1)){
- var overlappingEvents = getOverlappingEvents(event);
- var nextEvent = overlappingEvents[overlappingEvents.length-1];
- var nextDiv = getDiv(nextEvent);
- var orgWidth = parseInt(outerDiv.style.width);
- var newWidth = parseInt(nextDiv.style.left) - left;
- if (newWidth>orgWidth){
- log(" ++ Updating " + event.getSubject() + " width to " + newWidth + "% (was " + orgWidth + "%)");
- outerDiv.style.width = newWidth + "%";
- }
- }
-
- }
- }
-
- };
-
-
- //**************************************************************************
- //** removeEvent
- //**************************************************************************
-
- this.removeEvent = function(event){
-
- //Update event store
- store.remove(event);
-
-
- //Remove div
- if (event.numDays()>=1){
-
- for (var i=1; i0){
- var div = td.childNodes[0];
- if (div.event.equals(event)){
-
- //Remove event div
- td.removeChild(div);
-
-
- //Remove colspan
- var colSpan = td.colSpan;
- if (colSpan>=2){
- td.colSpan = 1;
- var nextSibling = td.nextSibling;
- var clone = td.cloneNode(true);
- //var tr = td.parentNode;
- for (var k=0; k1){
- var hasEvents = false;
- for (var k=1; k0){
- hasEvents = true;
- break;
- }
- }
-
- if (!hasEvents){
- multidayEventsTable.removeChild(tr);
- }
- }
-
-
- //Update table height
- var maxHeight = (multidayEventsTable.childNodes.length-1)*(eventHeight+eventPadding);
- if (maxHeight==0){
- multidayRow.style.display = "none";
- multidayRow.style.visibility = "hidden";
- }
- else{
- multidayEventsTable.parentNode.parentNode.parentNode.style.height = (maxHeight+(eventPadding)) + "px";
- }
-
-
- return;
- }
- }
- }
- }
-
- }
- else{
-
- //Remove event div
- var divs = getDivs(event.getStartDate());
- for (var i=0; i0){
-
- //Remove overlapping events
- for (var i=0; i1 && d.getDay()>0) d.setDate(d.getDate()-d.getDay());
- for (var i=0; i0){
- var div = td.childNodes[0];
- var event = div.event;
-
- var addEvent = true;
- for (var k=0; k1 && d.getDay()>0) d.setDate(d.getDate()-d.getDay());
- for (var i=0; i0){
- var div = td.childNodes[0];
- var event = div.event;
- store.remove(event);
- }
- }
-
- multidayEventsTable.removeChild(tr);
- }
- multidayRow.style.display = "none";
- multidayRow.style.visibility = "hidden";
- };
-
-
- //**************************************************************************
- //** refresh
- //**************************************************************************
- /** Used to re-render all the events in the cell.
- */
- this.refresh = function(){
- var events = me.getEvents();
- me.clear();
- me.addEvents(events);
- me.scrollTo(6.5);
- };
-
-
- //**************************************************************************
- //** addSingleDayEvent
- //**************************************************************************
- /** Used to render events that start and end on the same day.
- */
- var addSingleDayEvent = function(event, deferUpdates){
-
- if (deferUpdates!=true) deferUpdates = false;
-
- //Find cell used to render event
- var d = event.getStartDate();
- var cellID = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear();
- var cell = cells[cellID];
- if (cell==null) return;
-
-
-
- var width = 100;
- var overlappingEvents, rects, leftCoords;
-
-
-
- //Update position and width of any overlapping events
- if (!deferUpdates){
-
- //Get bounding rectangles of the overlapping events
- rects = [];
- var overlappingEvents = getOverlappingEvents(event);
- for (var i=0; i0){
-
- //Update position of the new div as needed
- log("Update " + event.getSubject() + "?");
- updatePosition(outerDiv, event, width);
-
-
- //If there are any divs to the right, ensure that the width spans
- //to the closest div
- var rightDiv;
- var r1 = _getRect(outerDiv);
- var _right = r1.right;
- for (var i=0; i=_right){
-
-
- for (var j=0; jendDate) return;
-
-
- var startColID, endColID;
- var continueLeft = false;
- var continueRight = false;
- var a = javaxt.dhtml.calendar.Utils.getDaysBetween(startDate, event.getStartDate());
- var b = javaxt.dhtml.calendar.Utils.getDaysBetween(startDate, event.getEndDate());
- //console.log(event.getSubject() + " " + a + " --> " + b);
-
- startColID = a;
- if (a<0){
- startColID = 0;
- continueLeft = true;
- }
- startColID = Math.floor(startColID);
-
-
-
- endColID = startColID+b;
- if (endColID>days-1){
- endColID = days-1;
- continueRight = true;
- }
- endColID = Math.floor(endColID);
-
-
- //console.log("use cols " + startColID + " - " + endColID);
-
-
-
- //Find start/end columns in the multiday events table
- var startCol, endCol;
- for (var i=1; i0){
- startCol = endCol = null;
- break;
- }
-
- previousCol = previousCol.previousSibling;
- }
- }
- }
-
-
- i = multidayEventsTable.childNodes.length;
- break;
- }
-
-
- var colSpan = td.colSpan;
- if (colSpan>=2) idx+=colSpan;
- else idx++;
- }
- }
-
-
-
- //Add new row to the multidayEventsTable as needed
- if (!startCol || !endCol){
- var tr = document.createElement("tr");
- multidayEventsTable.appendChild(tr);
- var td = document.createElement("td");
- td.className = "javaxt-cal-multiday-col-spacer";
- tr.appendChild(td);
-
-
- //Remove height from spacer col of previous row and set current col height
- if (multidayEventsTable.childNodes.length>1){
- tr.previousSibling.childNodes[0].style.height = '';
- }
- td.style.height = "100%";
-
-
-
- //Add days
- for (var i=0; i=2){
- javaxt.dhtml.calendar.Utils.addColSpan(startCol, colSpan);
- }
-
-
- //Add event to the startCol
- var outerDiv = document.createElement('div');
- outerDiv.style.width = "100%";
- outerDiv.style.height = "100%";
- outerDiv.style.position = "absolute";
-
- var innerDiv = document.createElement('div');
- innerDiv.style.height = "100%";
- var paddingLeft = continueLeft ? "0px" : eventPadding + "px";
- var paddingRight = continueRight ? "0px" : eventPadding + "px";
- innerDiv.style.padding = "0px " + paddingRight + " 0px " + paddingLeft; //Horizontal padding
- innerDiv.style.position = "relative";
- outerDiv.appendChild(innerDiv);
-
- var div = event.createDiv(continueLeft, continueRight);
- div.style.height = "100%";
- innerDiv.appendChild(div);
-
-
- //Wrap the outerdiv to ensure proper overflow
- var wrapper = document.createElement('div');
- wrapper.style.width = "100%";
- wrapper.style.height = eventHeight + "px";
- wrapper.style.position = "relative";
- wrapper.style.marginTop = (multidayEventsTable.childNodes.length>2 ? (eventPadding*2) : 0) + "px";
- wrapper.appendChild(outerDiv);
- wrapper.event = event;
- wrapper.onclick = function(e){
- var listener = me.getListener('eventclick');
- if (listener!=null){
- var callback = listener.callback;
- var scope = listener.scope;
- callback.apply(scope, [this.event, this, me, e]);
- }
- };
-
- startCol.appendChild(wrapper);
-
-
- //Update the visibility of the multiday row
- multidayRow.style.display = '';
- multidayRow.style.visibility = "visible";
-
-
- //Update table height
- var numMultiDayEvents = multidayEventsTable.childNodes.length-1;
- var h = numMultiDayEvents*(eventHeight+eventPadding);
- h = (h+(eventPadding*2));
- var multidayEventsDiv = multidayEventsTable.parentNode.parentNode.parentNode;
- multidayEventsDiv.style.height = h + "px";
-
-
-
-
- //Check whether the columns are aligned. For some browsers (e.g. Firefox),
- //the vertical scroll bar doesn't show up until the overflow container
- //reaches a certain height. Without the scroll bar, the multiday event
- //columns become misaligned with the cells.
- var h1 = multidayEventsTable.childNodes[0].childNodes[1];
- var c1;
- for (var id in cells) {
- if (cells.hasOwnProperty(id)) {
- c1 = cells[id].parentNode.parentNode;
- break;
- }
- }
-
- //Update the height of the vertical scroll bar until it becomes visible.
- //This should work assuming the multidayEventsTable and the header
- var orgHeight = h;
- while (h1.offsetWidth>c1.offsetWidth){
- if (h1 && startDate.getDay()>0) startDate.setDate(startDate.getDate()-startDate.getDay());
-
- endDate = new Date(startDate);
- endDate.setDate(endDate.getDate()+days);
-
-
- renderTable();
- loadEvents();
- };
-
-
- //**************************************************************************
- //** getDate
- //**************************************************************************
- this.getDate = function(){
- return date;
- };
-
-
- //**************************************************************************
- //** getDateRange
- //**************************************************************************
- /** Returns the start/end dates represented by this view. */
-
- this.getDateRange = function(){
- return {
- startDate: new Date(startDate),
- endDate: new Date(endDate)
- };
- };
-
-
- //**************************************************************************
- //** getTitle
- //**************************************************************************
- /** Returns a title for the current view. */
-
- this.getTitle = function(){
- if (days==1){
- var month = javaxt.dhtml.calendar.Utils.monthNames[date.getMonth()];
- return (month + " " + date.getDate() + ", " + date.getFullYear());
- }
- else{
- var range = me.getDateRange();
- var startDate = range.startDate;
- var endDate = range.endDate;
- endDate.setDate(endDate.getDate()-1);
-
- var startMonth = javaxt.dhtml.calendar.Utils.monthNames[startDate.getMonth()];
- var endMonth = javaxt.dhtml.calendar.Utils.monthNames[endDate.getMonth()];
-
- var a = startMonth + " " + startDate.getDate();
- var b = endDate.getDate() + ", " + endDate.getFullYear();
- if (startDate.getMonth()==endDate.getMonth() && startDate.getFullYear()==endDate.getFullYear()){
- return (a + " - " + b);
- }
- else{
- if (startDate.getFullYear()==endDate.getFullYear()){
- return (a + " - " + endMonth + " " + b);
- }
- else{
- return (a + ", " + startDate.getFullYear() + " - " + endMonth + " " + b);
- }
- }
- }
- };
-
-
- //**************************************************************************
- //** loadEvents
- //**************************************************************************
- var loadEvents = function(){
-
- var events = store.getEvents();
- for (var i=0; i=startDate.getTime()){
- me.addEvent(events[i]);
- }
- }
- };
-
-
- //**************************************************************************
- //** updatePosition
- //**************************************************************************
- /** Function used to move a div from left to right until we find an area
- * that doesn't intersect another div.
- */
- var updatePosition = function(div, _event, width){
-
- //Get bounding rectangle of the div. Subtract 1 pixel for intesection test.
- var r1 = _getRect(div);
- r1 = {
- left: r1.left+1,
- right: r1.right-1,
- top: r1.top+1,
- bottom: r1.bottom-1
- };
-
-
- //Check whether the div intersect any other divs. Shift div
- //to the right until we find a
- var divs = getDivs(_event.getStartDate());
- var x = 0;
- while (x<=100){
-
- var shiftRight = false;
- for (var j=0; j0){
- el = el.childNodes[0];
- if (el.tagName.toUpperCase()==="TD"){
- td = el;
- break;
- }
- }
-
- if (td){
- var left = parseInt(outerDiv.style.left);
- if (left===0){
- td.style.paddingLeft = eventPadding+"px";
- }
- else{
- td.style.paddingLeft = "0px";
- }
- }
- };
-
-
- //**************************************************************************
- //** sortEvents
- //**************************************************************************
- /** Used to sort events by order of appearance in the cell - left to right,
- * top to bottom.
- */
- var sortEvents = function(events){
-
- var _events = [];
- for (var i=0; i=30) startRow+=1;
- return startRow;
- };
-
-
- events.sort(function(event1, event2){
- var y1 = getRow(event1);
- var y2 = getRow(event2);
-
- var div1, div2;
- for (var i=0; i<_events.length; i++){
- if (_events[i].equals(event1)){
- div1 = divs[i];
- }
- if (_events[i].equals(event2)){
- div2 = divs[i];
- }
-
- if (div1!=null && div2!=null) break;
- }
- var x1 = parseInt(div1.style.left);
- var x2 = parseInt(div2.style.left);
-
- var a = x1 + (y1*1000);
- var b = x2 + (y2*1000);
- return a-b;
- });
-
- return events;
- };
-
-
- //**************************************************************************
- //** getOverlappingEvents
- //**************************************************************************
- /** Returns a list of events that overlap a given event. This method ignores
- * multi-day events and events that are not in the current view.
- */
- var getOverlappingEvents = function(event){
-
- var arr = [];
- var overlappingEvents = store.getOverlappingEvents(event);
- for (var i=0; i=1){
- //TODO: Find multiday event div...
- }
- else{
- var divs = getDivs(event.getStartDate());
- for (var i=0; i r1.right ||
- r2.right < r1.left ||
- r2.top > r1.bottom ||
- r2.bottom < r1.top);
- };
-
-
- //**************************************************************************
- //** createTable
- //**************************************************************************
- var createTable = function(){
- var table = document.createElement('table');
- table.style.width = "100%";
- table.style.height = "100%";
- table.cellSpacing = 0;
- table.cellPadding = 0;
- table.style.borderCollapse = "collapse";
- var tbody = document.createElement('tbody');
- table.appendChild(tbody);
- return tbody;
- };
-
-
- var _getRect = javaxt.dhtml.calendar.Utils.getRect;
- var initDrag = javaxt.dhtml.calendar.Utils.initDrag;
- var log = function(str){if(debug)console.log(str);};
-
-
- init();
+if(!javaxt) var javaxt={};
+if(!javaxt.dhtml) javaxt.dhtml={};
+if(!javaxt.dhtml.calendar) javaxt.dhtml.calendar={};
+
+//******************************************************************************
+//** Day View
+//*****************************************************************************/
+/**
+ * Used to render a day
+ *
+ ******************************************************************************/
+
+javaxt.dhtml.calendar.Day = function(parent, config) {
+ this.className = "javaxt.dhtml.calendar.Day";
+
+ var me = this;
+ var defaultConfig = {
+
+ };
+
+
+ //DOM elements
+ var el;
+ var bodyDiv;
+ var footerRow;
+ var multidayRow, multidayEventsTable;
+ var currTimeDiv, getCurrentDate;
+
+
+ //Class variables
+ var rendered;
+ var startDate, endDate;
+ var cells = {};
+ var widths = {};
+ var scrollWidth;
+ var scrollable = true;
+
+
+ //Config options
+ var days = 1;
+ var date;
+ var store;
+ var eventHeight = 17; //Only applies to multiday events
+ var eventPadding = 2;
+ var holdDelay = 500;
+ var debug = false;
+
+
+ //Browser detection used to adjust event padding
+ var isFirefox = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
+ var isIE = window.navigator.userAgent.indexOf("MSIE ") > -1 ||
+ window.navigator.userAgent.indexOf("Trident/") > -1;
+
+
+
+ //**************************************************************************
+ //** Constructor
+ //**************************************************************************
+ /** Creates a new instance of the calendar control. */
+
+ var init = function(){
+
+ //Clone the config so we don't modify the original config object
+ var clone = {};
+ merge(clone, config);
+
+
+ //Merge clone with default config
+ merge(clone, defaultConfig);
+ config = clone;
+
+
+ //Ensure the "javaxt-noselect" style rule is present in the document
+ javaxt.dhtml.utils.addNoSelectRule();
+
+
+
+ //Call super
+ new javaxt.dhtml.calendar.View(me, config);
+
+ //Set store
+ store = config.eventStore==null ? new javaxt.dhtml.calendar.EventStore() : config.eventStore;
+
+ //Set number of days to render
+ if (config.days!=null) days = config.days;
+
+ //Set event size, padding, and spacing
+ var isNumber = javaxt.dhtml.utils.isNumber;
+ if (isNumber(config.eventHeight)) eventHeight = parseInt(config.eventHeight);
+ if (isNumber(config.eventPadding)) eventPadding = parseInt(config.eventPadding);
+
+
+ //Specify function used to get current date
+ getCurrentDate = config.getCurrentDate==null ?
+ getCurrentDate = function(){return new Date();} : config.getCurrentDate;
+
+
+
+ //Configure renderers
+ if (config.renderers){
+ for (var rendererName in config.renderers) {
+ if (config.renderers.hasOwnProperty(rendererName)) {
+ if (me[rendererName]){
+
+ //Override the default renderer
+ (function(rendererName) {
+ me[rendererName] = function(){
+ var renderer = config.renderers[rendererName];
+ return renderer.apply(me, arguments);
+ };
+
+ })(rendererName);
+ }
+ }
+ }
+ }
+
+
+ //Call the beforerender callback
+ rendered = false;
+ var listener = me.getListener('beforerender');
+ if (listener!=null) listener.callback.apply(listener.scope, [me]);
+
+ //Set date and render the calendar
+ me.setDate(config.date);
+
+ //Call the afterrender callback
+ listener = me.getListener('afterrender');
+ if (listener!=null) listener.callback.apply(listener.scope, [me]);
+ rendered = true;
+
+ //Call the update callback
+ listener = me.getListener('update');
+ if (listener!=null) listener.callback.apply(listener.scope, [me]);
+
+
+ addResizeListener(parent, onResize);
+ };
+
+
+ //**************************************************************************
+ //** hasHours
+ //**************************************************************************
+ this.hasHours = function(){
+ return true;
+ };
+
+
+ //**************************************************************************
+ //** show
+ //**************************************************************************
+ this.show = function(){
+ parent.appendChild(el);
+ };
+
+
+ //**************************************************************************
+ //** hide
+ //**************************************************************************
+ this.hide = function(){
+ parent.removeChild(el);
+ };
+
+
+ //**************************************************************************
+ //** showFooter
+ //**************************************************************************
+ this.showFooter = function(){
+ footerRow.style.display = '';
+ };
+
+
+ //**************************************************************************
+ //** hideFooter
+ //**************************************************************************
+ this.hideFooter = function(){
+ footerRow.style.display = "none";
+ };
+
+
+ //**************************************************************************
+ //** enableTouch
+ //**************************************************************************
+ this.enableTouch = function(){
+ scrollable = true;
+ };
+
+
+ //**************************************************************************
+ //** disableTouch
+ //**************************************************************************
+ this.disableTouch = function(){
+ scrollable = false;
+ };
+
+
+ //**************************************************************************
+ //** onResize
+ //**************************************************************************
+ /** Recomputes event positions after the view is resized.
+ */
+ var onResize = function(){
+ if (!rendered) return;
+ if (!el || !el.parentNode) return; //view is hidden (not the active view)
+
+ //Re-lay-out the events (their positions are measured from the grid, which
+ //stretches/shrinks with the view). Scroll position is preserved.
+ var scrollTop = bodyDiv ? bodyDiv.scrollTop : null;
+ var events = me.getEvents();
+ me.clear();
+ me.addEvents(events);
+ if (scrollTop!=null) bodyDiv.scrollTop = scrollTop;
+ };
+
+
+
+ //**************************************************************************
+ //** renderTable
+ //**************************************************************************
+ /** Used to render a new table for the current date.
+ */
+ var renderTable = function(){
+
+
+ //Remove any previously rendered table
+ if (el!=null){
+ for (var i=0; i1) text = text.substring(0,3);
+ //else text+= ", " + config.monthNames[d.getMonth()] + " " + d.getDate();
+ innerDiv.innerHTML = text;
+
+
+ return outerDiv;
+ };
+
+
+
+ this.createColumnFooter = function(i){
+ return createElement('div');
+ };
+
+
+
+ //**************************************************************************
+ //** createHourLabel
+ //**************************************************************************
+ /** Returns a div used to indicate the hour of day (e.g. 12pm). This method
+ * can be safely overridden to generate custom labels for hours.
+ */
+ this.createHourLabel = function(hour){
+ var div = createElement('div', {
+ float: "right",
+ padding: "0px 7px 0px 7px"
+ });
+
+
+ //Update hour and set meridian
+ var meridian = 'AM';
+ if (hour==0) hour = 12;
+ else if (hour==12) meridian = 'PM';
+ else if (hour>12){
+ hour = (hour-12);
+ meridian = 'PM';
+ }
+
+ //Create table to render the hour and meridian
+ var hdr = createTable(div);
+ var tr = hdr.addRow();
+ var td = tr.addColumn();
+ addStyle(td, "labelHour");
+ td.innerHTML = hour;
+ td = tr.addColumn();
+ addStyle(td, "labelMeridian");
+ td.innerHTML = meridian;
+
+ return div;
+ };
+
+
+ //**************************************************************************
+ //** updateCurrTime
+ //**************************************************************************
+ /** Used to update the position of the current time indicator.
+ */
+ var updateCurrTime = function(){
+
+ var date = getCurrentDate();
+ var cellID = (date.getMonth()+1) + "-" + date.getDate() + "-" + date.getFullYear();
+ var cell = cells[cellID];
+ if (cell){
+
+ var hours = date.getHours();
+ var h1 = getVerticalOffset(hours, cell);
+ var h2 = getVerticalOffset(hours+1, cell);
+
+ var pixelsPerMinute = (h2-h1)/60;
+ var h = h1+(date.getMinutes()*pixelsPerMinute);
+
+ currTimeDiv.style.display = '';
+ currTimeDiv.style.top = h + "px";
+ }
+ else{
+ currTimeDiv.style.display = "none";
+ }
+ };
+
+
+ //**************************************************************************
+ //** scrollTo
+ //**************************************************************************
+ /** Used to scroll to a specific time of day.
+ * @param hour Time of day, specified as a decimal (e.g. 6.5 for 6:30 AM)
+ */
+ this.scrollTo = function(hour){
+ var cellID = (date.getMonth()+1) + "-" + date.getDate() + "-" + date.getFullYear();
+ var cell = cells[cellID];
+ var offset = getVerticalOffset(hour, cell);
+ bodyDiv.scrollTop = offset+1; //+1 for border
+ };
+
+
+ //**************************************************************************
+ //** getScrollDiv
+ //**************************************************************************
+ this.getScrollDiv = function(){
+ return bodyDiv;
+ };
+
+
+ //**************************************************************************
+ //** getVerticalOffset
+ //**************************************************************************
+ /** Returns the vertical offset (relative to the cell) of a given hour. The
+ * offset is measured directly from the rendered grid rows rather than summed
+ * from cached row heights - the rows stretch to fill the available space
+ * (e.g. on large screens) and fractional heights would otherwise accumulate
+ * rounding error, causing events to drift away from the hour lines.
+ */
+ var getVerticalOffset = function(hour, cell){
+ var tbody = cell.childNodes[0].childNodes[0];
+ var rows = tbody.childNodes;
+ if (rows.length==0) return 0;
+
+ var cellTop = _getRect(cell).top;
+
+ //There are 2 rows per hour; find the row that starts at (or just after)
+ //the requested hour and return the top of that row relative to the cell.
+ var i = Math.ceil(hour*2);
+ if (i < rows.length) return _getRect(rows[i]).top - cellTop;
+
+ //At/after the end of the day - use the bottom of the last row.
+ return _getRect(rows[rows.length-1]).bottom - cellTop;
+ };
+
+
+ //**************************************************************************
+ //** getEventStore
+ //**************************************************************************
+ this.getEventStore = function(){
+ return store;
+ };
+
+
+ //**************************************************************************
+ //** addEvent
+ //**************************************************************************
+ this.addEvent = function(event){
+ addEvent(event);
+ };
+
+
+ //**************************************************************************
+ //** addEvent
+ //**************************************************************************
+ /** Private method used to add an event to the view.
+ * @param deferUpdates Option to postpone or defer updating the event
+ * width and position when there are overlapping events. This option is
+ * provided for optimization.
+ */
+ var addEvent = function(event, deferUpdates){
+
+ log("Adding " + event.getSubject() + "...");
+ var numDays = event.numDays();
+
+
+ //Check if we've already rendered the given event
+ if (numDays>=1){
+ for (var i=1; i0){
+ var div = td.childNodes[0];
+ if (div.event.equals(event)){
+ return;
+ }
+ }
+ }
+ }
+
+ }
+ else{
+ var divs = getDivs(event.getStartDate());
+ for (var i=0; i=1){
+ addMultiDayEvent(event);
+ }
+ else{
+ addSingleDayEvent(event, deferUpdates);
+ }
+
+
+ //Update the event store
+ store.add(event);
+ };
+
+
+ //**************************************************************************
+ //** addEvents
+ //**************************************************************************
+ /** Used to add multiple events to the view. This method is recommended for
+ * bulk loading and is significantly faster than calling addEvent multiple
+ * times.
+ */
+ this.addEvents = function(events){
+
+
+ //Add events but defer updating position and widths of overlapping events
+ for (var i=0; i0){
+
+ //Compute number of columns
+ var numColumns = 2;
+ if (overlappingEvents.length>1){
+
+ //The following logic is somewhat flawed and may yeild
+ //a column count slightly higher than expected...
+ for (var j=0; j0){
+ var _numColumns = 1 + 1 + numIntersects;
+ if (_numColumns>numColumns) numColumns = _numColumns;
+ }
+
+ }
+ }
+
+
+ var width = (Math.floor((1/(numColumns))*100));
+ log("Update " + event.getSubject() + " width to " + width + "%");
+ var outerDiv = getDiv(event);
+ outerDiv.style.width = width + "%";
+
+
+ for (var j=0; j0){
+
+ for (var j=0; j1){
+
+ var left = parseInt(outerDiv.style.left);
+ var colIndex = Math.floor(left/width)+1;
+
+
+ log(event.getSubject() + " is in column " + colIndex + "/" + numColumns);
+
+ if (colIndex===(numColumns-1)){
+ var overlappingEvents = getOverlappingEvents(event);
+ var nextEvent = overlappingEvents[overlappingEvents.length-1];
+ var nextDiv = getDiv(nextEvent);
+ var orgWidth = parseInt(outerDiv.style.width);
+ var newWidth = parseInt(nextDiv.style.left) - left;
+ if (newWidth>orgWidth){
+ log(" ++ Updating " + event.getSubject() + " width to " + newWidth + "% (was " + orgWidth + "%)");
+ outerDiv.style.width = newWidth + "%";
+ }
+ }
+
+ }
+ }
+
+ };
+
+
+ //**************************************************************************
+ //** removeEvent
+ //**************************************************************************
+
+ this.removeEvent = function(event){
+
+ //Update event store
+ store.remove(event);
+
+
+ //Remove div
+ if (event.numDays()>=1){
+
+ for (var i=1; i0){
+ var div = td.childNodes[0];
+ if (div.event.equals(event)){
+
+ //Remove event div
+ td.removeChild(div);
+
+
+ //Remove colspan
+ var colSpan = td.colSpan;
+ if (colSpan>=2){
+ td.colSpan = 1;
+ var nextSibling = td.nextSibling;
+ var clone = td.cloneNode(true);
+ //var tr = td.parentNode;
+ for (var k=0; k1){
+ var hasEvents = false;
+ for (var k=1; k0){
+ hasEvents = true;
+ break;
+ }
+ }
+
+ if (!hasEvents){
+ multidayEventsTable.removeChild(tr);
+ }
+ }
+
+
+ //Update table height
+ var maxHeight = (multidayEventsTable.childNodes.length-1)*(eventHeight+eventPadding);
+ if (maxHeight==0){
+ multidayRow.style.display = "none";
+ multidayRow.style.visibility = "hidden";
+ }
+ else{
+ multidayEventsTable.parentNode.parentNode.parentNode.style.height = (maxHeight+(eventPadding)) + "px";
+ }
+
+
+ return;
+ }
+ }
+ }
+ }
+
+ }
+ else{
+
+ //Remove event div
+ var divs = getDivs(event.getStartDate());
+ for (var i=0; i0){
+
+ //Remove overlapping events
+ for (var i=0; i1 && d.getDay()>0) d.setDate(d.getDate()-d.getDay());
+ for (var i=0; i0){
+ var div = td.childNodes[0];
+ var event = div.event;
+
+ var addEvent = true;
+ for (var k=0; k1 && d.getDay()>0) d.setDate(d.getDate()-d.getDay());
+ for (var i=0; i0){
+ var div = td.childNodes[0];
+ var event = div.event;
+ store.remove(event);
+ }
+ }
+
+ multidayEventsTable.removeChild(tr);
+ }
+ multidayRow.style.display = "none";
+ multidayRow.style.visibility = "hidden";
+ };
+
+
+ //**************************************************************************
+ //** refresh
+ //**************************************************************************
+ /** Used to re-render all the events in the cell.
+ */
+ this.refresh = function(){
+ var events = me.getEvents();
+ me.clear();
+ me.addEvents(events);
+ me.scrollTo(6.5);
+ };
+
+
+ //**************************************************************************
+ //** addSingleDayEvent
+ //**************************************************************************
+ /** Used to render events that start and end on the same day.
+ */
+ var addSingleDayEvent = function(event, deferUpdates){
+
+ if (deferUpdates!=true) deferUpdates = false;
+
+ //Find cell used to render event
+ var d = event.getStartDate();
+ var cellID = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear();
+ var cell = cells[cellID];
+ if (cell==null) return;
+
+
+
+ var width = 100;
+ var overlappingEvents, rects, leftCoords;
+
+
+
+ //Update position and width of any overlapping events
+ if (!deferUpdates){
+
+ //Get bounding rectangles of the overlapping events
+ rects = [];
+ var overlappingEvents = getOverlappingEvents(event);
+ for (var i=0; i0){
+
+ //Update position of the new div as needed
+ log("Update " + event.getSubject() + "?");
+ updatePosition(outerDiv, event, width);
+
+
+ //If there are any divs to the right, ensure that the width spans
+ //to the closest div
+ var rightDiv;
+ var r1 = _getRect(outerDiv);
+ var _right = r1.right;
+ for (var i=0; i=_right){
+
+
+ for (var j=0; jendDate) return;
+
+
+ var startColID, endColID;
+ var continueLeft = false;
+ var continueRight = false;
+ var a = javaxt.dhtml.calendar.Utils.getDaysBetween(startDate, event.getStartDate());
+ var b = javaxt.dhtml.calendar.Utils.getDaysBetween(startDate, event.getEndDate());
+ //console.log(event.getSubject() + " " + a + " --> " + b);
+
+ startColID = a;
+ if (a<0){
+ startColID = 0;
+ continueLeft = true;
+ }
+ startColID = Math.floor(startColID);
+
+
+
+ endColID = startColID+b;
+ if (endColID>days-1){
+ endColID = days-1;
+ continueRight = true;
+ }
+ endColID = Math.floor(endColID);
+
+
+ //console.log("use cols " + startColID + " - " + endColID);
+
+
+
+ //Find start/end columns in the multiday events table
+ var startCol, endCol;
+ for (var i=1; i0){
+ startCol = endCol = null;
+ break;
+ }
+
+ previousCol = previousCol.previousSibling;
+ }
+ }
+ }
+
+
+ i = multidayEventsTable.childNodes.length;
+ break;
+ }
+
+
+ var colSpan = td.colSpan;
+ if (colSpan>=2) idx+=colSpan;
+ else idx++;
+ }
+ }
+
+
+
+ //Add new row to the multidayEventsTable as needed
+ if (!startCol || !endCol){
+ var tr = createElement("tr", multidayEventsTable);
+ var td = createElement("td", tr);
+ addStyle(td, "multidayColSpacer");
+
+
+ //Remove height from spacer col of previous row and set current col height
+ if (multidayEventsTable.childNodes.length>1){
+ tr.previousSibling.childNodes[0].style.height = '';
+ }
+ td.style.height = "100%";
+
+
+
+ //Add days
+ for (var i=0; i=2){
+ javaxt.dhtml.calendar.Utils.addColSpan(startCol, colSpan);
+ }
+
+
+ //Add event to the startCol
+ var outerDiv = createElement('div', {
+ width: "100%",
+ height: "100%",
+ position: "absolute"
+ });
+
+ var paddingLeft = continueLeft ? "0px" : eventPadding + "px";
+ var paddingRight = continueRight ? "0px" : eventPadding + "px";
+ var innerDiv = createElement('div', outerDiv, {
+ height: "100%",
+ padding: "0px " + paddingRight + " 0px " + paddingLeft, //Horizontal padding
+ position: "relative"
+ });
+
+ var div = event.createDiv(continueLeft, continueRight);
+ div.style.height = "100%";
+ innerDiv.appendChild(div);
+
+
+ //Wrap the outerdiv to ensure proper overflow
+ var wrapper = createElement('div', {
+ width: "100%",
+ height: eventHeight + "px",
+ position: "relative",
+ marginTop: (multidayEventsTable.childNodes.length>2 ? (eventPadding*2) : 0) + "px"
+ });
+ wrapper.appendChild(outerDiv);
+ wrapper.event = event;
+ wrapper.onclick = function(e){
+ var listener = me.getListener('eventclick');
+ if (listener!=null){
+ var callback = listener.callback;
+ var scope = listener.scope;
+ callback.apply(scope, [this.event, this, me, e]);
+ }
+ };
+
+ startCol.appendChild(wrapper);
+
+
+ //Update the visibility of the multiday row
+ multidayRow.style.display = '';
+ multidayRow.style.visibility = "visible";
+
+
+ //Update table height
+ var numMultiDayEvents = multidayEventsTable.childNodes.length-1;
+ var h = numMultiDayEvents*(eventHeight+eventPadding);
+ h = (h+(eventPadding*2));
+ var multidayEventsDiv = multidayEventsTable.parentNode.parentNode.parentNode;
+ multidayEventsDiv.style.height = h + "px";
+
+
+
+
+ //Check whether the columns are aligned. For some browsers (e.g. Firefox),
+ //the vertical scroll bar doesn't show up until the overflow container
+ //reaches a certain height. Without the scroll bar, the multiday event
+ //columns become misaligned with the cells.
+ var h1 = multidayEventsTable.childNodes[0].childNodes[1];
+ var c1;
+ for (var id in cells) {
+ if (cells.hasOwnProperty(id)) {
+ c1 = cells[id].parentNode.parentNode;
+ break;
+ }
+ }
+
+ //Update the height of the vertical scroll bar until it becomes visible.
+ //This should work assuming the multidayEventsTable and the header
+ var orgHeight = h;
+ while (h1.offsetWidth>c1.offsetWidth){
+ if (h1 && startDate.getDay()>0) startDate.setDate(startDate.getDate()-startDate.getDay());
+
+ endDate = new Date(startDate);
+ endDate.setDate(endDate.getDate()+days);
+
+
+ renderTable();
+ loadEvents();
+ };
+
+
+ //**************************************************************************
+ //** getDate
+ //**************************************************************************
+ this.getDate = function(){
+ return date;
+ };
+
+
+ //**************************************************************************
+ //** getDateRange
+ //**************************************************************************
+ /** Returns the start/end dates represented by this view. */
+
+ this.getDateRange = function(){
+ return {
+ startDate: new Date(startDate),
+ endDate: new Date(endDate)
+ };
+ };
+
+
+ //**************************************************************************
+ //** getTitle
+ //**************************************************************************
+ /** Returns a title for the current view. */
+
+ this.getTitle = function(){
+ if (days==1){
+ var month = config.monthNames[date.getMonth()];
+ return (month + " " + date.getDate() + ", " + date.getFullYear());
+ }
+ else{
+ var range = me.getDateRange();
+ var startDate = range.startDate;
+ var endDate = range.endDate;
+ endDate.setDate(endDate.getDate()-1);
+
+ var startMonth = config.monthNames[startDate.getMonth()];
+ var endMonth = config.monthNames[endDate.getMonth()];
+
+ var a = startMonth + " " + startDate.getDate();
+ var b = endDate.getDate() + ", " + endDate.getFullYear();
+ if (startDate.getMonth()==endDate.getMonth() && startDate.getFullYear()==endDate.getFullYear()){
+ return (a + " - " + b);
+ }
+ else{
+ if (startDate.getFullYear()==endDate.getFullYear()){
+ return (a + " - " + endMonth + " " + b);
+ }
+ else{
+ return (a + ", " + startDate.getFullYear() + " - " + endMonth + " " + b);
+ }
+ }
+ }
+ };
+
+
+ //**************************************************************************
+ //** loadEvents
+ //**************************************************************************
+ var loadEvents = function(){
+
+ var events = store.getEvents();
+ for (var i=0; i=startDate.getTime()){
+ me.addEvent(events[i]);
+ }
+ }
+ };
+
+
+ //**************************************************************************
+ //** updatePosition
+ //**************************************************************************
+ /** Function used to move a div from left to right until we find an area
+ * that doesn't intersect another div.
+ */
+ var updatePosition = function(div, _event, width){
+
+ //Get bounding rectangle of the div. Subtract 1 pixel for intesection test.
+ var r1 = _getRect(div);
+ r1 = {
+ left: r1.left+1,
+ right: r1.right-1,
+ top: r1.top+1,
+ bottom: r1.bottom-1
+ };
+
+
+ //Check whether the div intersect any other divs. Shift div
+ //to the right until we find a
+ var divs = getDivs(_event.getStartDate());
+ var x = 0;
+ while (x<=100){
+
+ var shiftRight = false;
+ for (var j=0; j0){
+ el = el.childNodes[0];
+ if (el.tagName.toUpperCase()==="TD"){
+ td = el;
+ break;
+ }
+ }
+
+ if (td){
+ var left = parseInt(outerDiv.style.left);
+ if (left===0){
+ td.style.paddingLeft = eventPadding+"px";
+ }
+ else{
+ td.style.paddingLeft = "0px";
+ }
+ }
+ };
+
+
+ //**************************************************************************
+ //** sortEvents
+ //**************************************************************************
+ /** Used to sort events by order of appearance in the cell - left to right,
+ * top to bottom.
+ */
+ var sortEvents = function(events){
+
+ var _events = [];
+ for (var i=0; i=30) startRow+=1;
+ return startRow;
+ };
+
+
+ events.sort(function(event1, event2){
+ var y1 = getRow(event1);
+ var y2 = getRow(event2);
+
+ var div1, div2;
+ for (var i=0; i<_events.length; i++){
+ if (_events[i].equals(event1)){
+ div1 = divs[i];
+ }
+ if (_events[i].equals(event2)){
+ div2 = divs[i];
+ }
+
+ if (div1!=null && div2!=null) break;
+ }
+ var x1 = parseInt(div1.style.left);
+ var x2 = parseInt(div2.style.left);
+
+ var a = x1 + (y1*1000);
+ var b = x2 + (y2*1000);
+ return a-b;
+ });
+
+ return events;
+ };
+
+
+ //**************************************************************************
+ //** getOverlappingEvents
+ //**************************************************************************
+ /** Returns a list of events that overlap a given event. This method ignores
+ * multi-day events and events that are not in the current view.
+ */
+ var getOverlappingEvents = function(event){
+
+ var arr = [];
+ var overlappingEvents = store.getOverlappingEvents(event);
+ for (var i=0; i=1){
+ //TODO: Find multiday event div...
+ }
+ else{
+ var divs = getDivs(event.getStartDate());
+ for (var i=0; idate.getMonth()) td.className+='-next-month';
-
-
-
-
- //Update list of cells
- var id = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear();
- var cell = cells[id];
- if (cell==null){
- cells[id] = [td];
- }
- else{
- cells[id].push(td);
- }
-
-
-
- //Add event listener
- td.date = new Date(d);
- if (x==1){
- td.onclick = function(e){
- var el = this;
- var clickedEvent = false;
- var div = el.childNodes[0];
- if (div.childNodes.length>0){
- var firstEvent = _getRect(div.childNodes[0]);
- var lastEvent = div.childNodes.length==1 ?
- firstEvent : _getRect(div.childNodes[div.childNodes.length-1]);
- var y = e.clientY;
- if (ylastEvent.bottom){}
- else clickedEvent = true;
- }
-
-
- if (!clickedEvent){
- var _date = new Date(el.date);
- var listener = me.getListener('cellclick');
- if (listener!=null){
- var callback = listener.callback;
- var scope = listener.scope;
- callback.apply(scope, [_date, el, me, e]);
- }
- }
- };
- }
- else{
- td.onclick = function(e){
- var el = this;
- var _date = new Date(el.date);
- var listener = me.getListener('cellclick');
- if (listener!=null){
- var callback = listener.callback;
- var scope = listener.scope;
- callback.apply(scope, [_date, el, me, e]);
- }
- };
- }
-
-
- td.ontouchstart = function(e) {
-
- //Disable select/highlight behaviour
- e.preventDefault();
-
-
- //Call onclick function
- if (touchEnabled){
- var touch = e.touches[0];
- var x = touch.pageX;
- var y = touch.pageY;
- this.onclick.apply(this, [{
- clientX: x,
- clientY: y
- }]);
- }
- };
-
-
-
- if (x==0){
- td.className += " javaxt-cal-cell-header";
- td.appendChild(me.createCellHeader(new Date(d), i, j));
- }
- else if (x==1){
-
- if (k==0){
- multiDayCols[id] = [td];
- td.style.height = "1px";
- }
- else{
- div = document.createElement("div");
- div.style.width="100%";
- div.style.height = "100%";
- div.style.position = "relative";
- td.appendChild(div);
- singleDayCols[id] = td;
- }
- }
- else if (x==2){
- td.className += " javaxt-cal-cell-footer";
- td.style.height = "1px";
- td.appendChild(me.createCellFooter(new Date(d), i, j));
- }
-
- d.setDate(d.getDate()+1);
-
-
-
-
- //Remove borders as needed. The borders for these
- //specific cells should be set by javaxt-cal-body
- if (j==0){
- td.style.borderLeft = "0px";
- }
- if (j==days.length-1){
- td.style.borderRight = "0px";
- }
- if (i==0){
- td.style.borderTop = "0px";
- }
- if (i==numWeeks-1){
- td.style.borderBottom = "0px";
- }
-
-
-
- tr.appendChild(td);
- }
- }
- }
- }
-
- body.appendChild(tbody.parentNode);
- parent.appendChild(table);
-
-
-
-
- //Call the update callback
- if (rendered){
- var listener = me.getListener('update');
- if (listener!=null) listener.callback.apply(listener.scope, [me]);
- }
- };
-
-
- //**************************************************************************
- //** createColumnHeader
- //**************************************************************************
- /** Returns a div used to indicate the day of the week. The div is inserted
- * into a given column header. This method can be safely overridden to
- * generate custom headers.
- */
- this.createColumnHeader = function(i){
- var outerDiv = document.createElement('div');
- outerDiv.style.width = "100%";
- outerDiv.style.height = "100%";
- outerDiv.style.position = "relative";
- var innerDiv = document.createElement('div');
- innerDiv.style.width = "100%";
- innerDiv.style.height = "100%";
- innerDiv.style.position = "absolute";
- innerDiv.style.whiteSpace = 'nowrap';
- innerDiv.style.overflow = 'hidden';
- innerDiv.innerHTML = days[i];
-
- outerDiv.appendChild(innerDiv);
- return outerDiv;
- };
-
-
- //**************************************************************************
- //** createCellHeader
- //**************************************************************************
- /** Returns a div used to indicate the date within an individual cell. This
- * method can be safely overridden to generate custom cell headers.
- */
- this.createCellHeader = function(date, i, j){
- var text = date.getDate();
- var monthName = javaxt.dhtml.calendar.Utils.monthNames[date.getMonth()].substring(0,3) + " ";
- if (i==0 && j==0) text = monthName + text;
- else if (text==1) text = monthName + text;
- var div = document.createElement("div");
- div.innerHTML = text;
- return div;
- };
-
-
- //**************************************************************************
- //** createCellFooter
- //**************************************************************************
- /** Returns an div empty which is enserted into the cell footer. This method
- * can be safely overridden to generate custom cell footers.
- */
- this.createCellFooter = function(date, i, j){
- var div = document.createElement("div");
- return div;
- };
-
-
- //**************************************************************************
- //** getCells
- //**************************************************************************
- /** Returns an array of cells - one for each day in the view. A cell is
- * defined by a date/id and a bounding rectangle. These cells are used
- * when dragging events.
- */
- this.getCells = function(){
- var arr = [];
- for (var id in cells) {
- if (cells.hasOwnProperty(id)) {
- var cols = cells[id];
-
- var r1 = _getRect(cols[0]);
- var r2 = _getRect(cols[cols.length-1]);
- var x1 = r1.left;
- var x2 = r1.right;
- var y1 = r1.top;
- var y2 = r2.bottom;
-
-
- var rect = {
- left: x1,
- right: x2,
- top: y1,
- bottom: y2,
- width: x2-x1,
- height: y2-y1
- };
-
- arr.push({
- id: id,
- rect: rect
- });
- }
- }
- return arr;
- };
-
-
- //**************************************************************************
- //** getEventStore
- //**************************************************************************
- this.getEventStore = function(){
- return store;
- };
-
-
- //**************************************************************************
- //** addEvent
- //**************************************************************************
- this.addEvent = function(event){
-
-
- log("Adding " + event.getSubject() + "...");
-
-
- //Check if we've already rendered the given event
- var numDays = event.numDays();
- if (numDays>=1){
- for (var id in multiDayCols) {
- if (multiDayCols.hasOwnProperty(id)) {
- var arr = multiDayCols[id];
- if (arr!=null){
- for (var i=1; i=1){
- addMultiDayEvent(event);
- }
- else{
- addSingleDayEvent(event);
- }
-
-
- //Update the event store
- store.add(event);
- };
-
-
-
- //**************************************************************************
- //** removeEvent
- //**************************************************************************
-
- this.removeEvent = function(event){
-
-
- //Remove the event from the store
- store.remove(event);
-
-
- //Update view
- if (event.numDays()>=1){ //Multiday Event
-
-
- //Remove div
- var rows = [];
- for (var id in multiDayCols) {
- if (multiDayCols.hasOwnProperty(id)) {
- var arr = multiDayCols[id];
- for (var i=1; i=2){
- var str = id.split("-");
- var month = parseInt(str[0]);
- var day = parseInt(str[1]);
- var year = parseInt(str[2]);
- var date = new Date(year, month-1, day);
- for (var x=1; x1){
- var td = arr[1];
- if (td.parentNode!=null){
- if (td.parentNode.previousSibling==firstRow){
- var eventDiv = td.firstChild;
- if (eventDiv!=null){
- eventDiv.style.marginTop = "0px";
- }
- }
- }
- }
- }
- }
-
- }
- }
-
-
-
- alignEvents();
-
- }
- else{ //Single day event
-
- var d = event.getStartDate();
- var id = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear();
- var td = singleDayCols[id];
- var div = td.childNodes[0];
- for (var i=0; i=event.getStartDate().getTime()){
-
- div.insertBefore(wrapper, div.childNodes[i]);
- addedEvent = true;
- break;
- }
- }
- }
- if (!addedEvent){
- div.appendChild(wrapper);
- }
-
- };
-
-
- //**************************************************************************
- //** addMultiDayEvent
- //**************************************************************************
- /** Used to render multi-day events
- */
- var addMultiDayEvent = function(event){
-
-
- //Find cells to span
- var cols = [];
- for (var x=0; x0) spans.push(span);
-
-
- //Iterate through the spans and render events
- for (var i=0; i1){
-
- for (k=1; k0 || _col.parentNode==null){
- spanInUse = true;
- break;
- }
-
- }
-
- if (!spanInUse){
- col = cols[0][k];
- break;
- }
- }
- }
-
-
- //If a suitable column was not found, add a new row and select a
- //column from the new row
- if (col==null){
-
- //Insert row
- var currRow = cols[0][cols[0].length-1].parentNode;
- var nextRow = currRow.nextSibling;
- var newRow = cols[0][0].parentNode.cloneNode(true);
- currRow.parentNode.insertBefore(newRow, nextRow);
-
- //Update the multiDayCols array
- for (var j=0; j0 && col.previousSibling==null;
- var continueRight = col.nextSibling==null && (spans.length>1 && i1) wrapper.style.marginTop = (eventSpacing*2) + "px"; //Vertical padding
- wrapper.appendChild(outerDiv);
- wrapper.event = event;
- wrapper.onclick = function(e){
- var listener = me.getListener('eventclick');
- if (listener!=null){
- var callback = listener.callback;
- var scope = listener.scope;
- callback.apply(scope, [this.event, this, me, e]);
- }
- };
-
-
- col.appendChild(wrapper);
- col.style.height = eventHeight + "px";
- mutiDayEventDivs.push(wrapper);
- }
-
-
-
- //Move the event divs up to fill the gap created by multi-events
- alignEvents();
- };
-
-
- //**************************************************************************
- //** alignEvents
- //**************************************************************************
- /** Used to vertically align events to fill any gaps created by multi-events.
- * If there are multiday events that span a cell, events are aligned with
- * the last multiday event in the cell. Otherwise, events are aligned with
- * the cell header
- */
- var alignEvents = function(){
- for (var id in singleDayCols) {
- if (singleDayCols.hasOwnProperty(id)) {
- var td = singleDayCols[id];
- var div = td.childNodes[0];
- var cols = multiDayCols[id];
- var offset = 0;
-
-
-
- var numUsedCols = 0;
- var lastUsedCol = null;
- for (var i=1; i0 || cols[i].parentNode==null){
- numUsedCols++;
- lastUsedCol = i;
- }
- }
-
-
- //The following logic doesn't work for FireFox:
- //var offset = (((cols.length-1)-numUsedCols)*(eventHeight));
- //if (numUsedCols==0) offset+=eventSpacing;
- //As a workaround, we need to compute offset using the DOM
- var tr = td.parentNode;
- var y2 = _getRect(tr).top;
- var y1;
- if (lastUsedCol!=null){
- var lastUsedRow = tr.parentNode.childNodes[lastUsedCol];
- y1 = _getRect(lastUsedRow).bottom;
- offset = y2-y1;
- }
- else{
- y1 = _getRect(tr.parentNode.childNodes[0]).top;
- offset = ((y2-y1)+((eventSpacing*2)-1)); //-1px for spacer row?
- }
-
-
- div.style.marginTop = -offset + "px";
- }
- }
- };
-
-
- //**************************************************************************
- //** createTable
- //**************************************************************************
- var createTable = function(){
- var table = document.createElement('table');
- table.style.width = "100%";
- table.style.height = "100%";
- table.cellSpacing = 0;
- table.cellPadding = 0;
- table.style.borderCollapse = "collapse";
- var tbody = document.createElement('tbody');
- table.appendChild(tbody);
- return tbody;
- };
-
-
-
-
-
- //**************************************************************************
- //** getDOM
- //**************************************************************************
- this.getDOM = function(){
- return table;
- };
-
-
- //**************************************************************************
- //** next
- //**************************************************************************
- this.next = function(){
- date.setMonth(date.getMonth()+1);
- computeRange(date);
- renderTable();
- loadEvents();
- };
-
-
- //**************************************************************************
- //** back
- //**************************************************************************
- this.back = function(){
- date.setMonth(date.getMonth()-1);
- computeRange(date);
- renderTable();
- loadEvents();
- };
-
-
- //**************************************************************************
- //** setDate
- //**************************************************************************
- this.setDate = function(d){
- if (d==null) d = new Date();
-
-
- if (date!=null){
- if (d.getFullYear()==date.getFullYear() && d.getMonth()==date.getMonth()){
- date = d;
- return;
- }
- }
-
-
- date = d;
- computeRange(date);
- renderTable();
- loadEvents();
- };
-
-
- //**************************************************************************
- //** getDate
- //**************************************************************************
- this.getDate = function(){
- return date;
- };
-
-
- //**************************************************************************
- //** computeRange
- //**************************************************************************
- var computeRange = function(d){
-
- //Compute number of rows to render. Credit:
- //http://stackoverflow.com/a/2485172
- var year = d.getFullYear();
- var month = d.getMonth()+1;
- var firstOfMonth = new Date(year, month-1, 1);
- var lastOfMonth = new Date(year, month, 0);
- numWeeks = Math.ceil( (firstOfMonth.getDay() + lastOfMonth.getDate()) / 7);
-
- startDate = new Date(firstOfMonth);
- startDate.setDate(startDate.getDate()-firstOfMonth.getDay());
-
- endDate = new Date(lastOfMonth);
- endDate.setDate(endDate.getDate()+(6-lastOfMonth.getDay()));
- };
-
-
- //**************************************************************************
- //** getDateRange
- //**************************************************************************
- /** Returns the start/end dates represented by this view. */
-
- this.getDateRange = function(){
- return {
- startDate: new Date(startDate),
- endDate: new Date(endDate)
- };
- };
-
-
- //**************************************************************************
- //** getTitle
- //**************************************************************************
- /** Returns a title for the current view. */
-
- this.getTitle = function(){
- return (javaxt.dhtml.calendar.Utils.monthNames[date.getMonth()] + " " + date.getFullYear());
- };
-
-
- //**************************************************************************
- //** loadEvents
- //**************************************************************************
- var loadEvents = function(){
- var events = store.getEvents();
- for (var i=0; idate.getMonth()) addStyle(td, "cellNextMonth");
+ else addStyle(td, "cell");
+
+
+
+
+ //Update list of cells
+ var id = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear();
+ var cell = cells[id];
+ if (cell==null){
+ cells[id] = [td];
+ }
+ else{
+ cells[id].push(td);
+ }
+
+
+
+ //Add event listener
+ td.date = new Date(d);
+ if (x==1){
+ td.onclick = function(e){
+ var el = this;
+ var clickedEvent = false;
+ var div = el.childNodes[0];
+ if (div.childNodes.length>0){
+ var firstEvent = _getRect(div.childNodes[0]);
+ var lastEvent = div.childNodes.length==1 ?
+ firstEvent : _getRect(div.childNodes[div.childNodes.length-1]);
+ var y = e.clientY;
+ if (ylastEvent.bottom){}
+ else clickedEvent = true;
+ }
+
+
+ if (!clickedEvent){
+ var _date = new Date(el.date);
+ var listener = me.getListener('cellclick');
+ if (listener!=null){
+ var callback = listener.callback;
+ var scope = listener.scope;
+ callback.apply(scope, [_date, el, me, e]);
+ }
+ }
+ };
+ }
+ else{
+ td.onclick = function(e){
+ var el = this;
+ var _date = new Date(el.date);
+ var listener = me.getListener('cellclick');
+ if (listener!=null){
+ var callback = listener.callback;
+ var scope = listener.scope;
+ callback.apply(scope, [_date, el, me, e]);
+ }
+ };
+ }
+
+
+ td.ontouchstart = function(e) {
+
+ //Disable select/highlight behaviour
+ e.preventDefault();
+
+
+ //Call onclick function
+ if (touchEnabled){
+ var touch = e.touches[0];
+ var x = touch.pageX;
+ var y = touch.pageY;
+ this.onclick.apply(this, [{
+ clientX: x,
+ clientY: y
+ }]);
+ }
+ };
+
+
+
+ if (x==0){
+ addStyle(td, "cellHeader");
+ td.appendChild(me.createCellHeader(new Date(d), i, j));
+ }
+ else if (x==1){
+
+ if (k==0){
+ multiDayCols[id] = [td];
+ td.style.height = "1px";
+ }
+ else{
+ div = createElement("div", td, {
+ width: "100%",
+ height: "100%",
+ position: "relative"
+ });
+ singleDayCols[id] = td;
+ }
+ }
+ else if (x==2){
+ addStyle(td, "cellFooter");
+ td.style.height = "1px";
+ td.appendChild(me.createCellFooter(new Date(d), i, j));
+ }
+
+ d.setDate(d.getDate()+1);
+
+
+
+
+ //Remove borders as needed. The borders for these
+ //specific cells should be set by javaxt-cal-body
+ if (j==0){
+ td.style.borderLeft = "0px";
+ }
+ if (j==days.length-1){
+ td.style.borderRight = "0px";
+ }
+ if (i==0){
+ td.style.borderTop = "0px";
+ }
+ if (i==numWeeks-1){
+ td.style.borderBottom = "0px";
+ }
+
+
+
+ tr.appendChild(td);
+ }
+ }
+ }
+ }
+
+
+
+
+
+ //Call the update callback
+ if (rendered){
+ var listener = me.getListener('update');
+ if (listener!=null) listener.callback.apply(listener.scope, [me]);
+ }
+ };
+
+
+ //**************************************************************************
+ //** createColumnHeader
+ //**************************************************************************
+ /** Returns a div used to indicate the day of the week. The div is inserted
+ * into a given column header. This method can be safely overridden to
+ * generate custom headers.
+ */
+ this.createColumnHeader = function(i){
+ var outerDiv = createElement('div', {
+ width: "100%",
+ height: "100%",
+ position: "relative"
+ });
+ var innerDiv = createElement('div', outerDiv, {
+ width: "100%",
+ height: "100%",
+ position: "absolute",
+ whiteSpace: 'nowrap',
+ overflow: 'hidden'
+ });
+ innerDiv.innerHTML = days[i];
+ return outerDiv;
+ };
+
+
+ //**************************************************************************
+ //** createCellHeader
+ //**************************************************************************
+ /** Returns a div used to indicate the date within an individual cell. This
+ * method can be safely overridden to generate custom cell headers.
+ */
+ this.createCellHeader = function(date, i, j){
+ var text = date.getDate();
+ var monthName = config.monthNames[date.getMonth()].substring(0,3) + " ";
+ if (i==0 && j==0) text = monthName + text;
+ else if (text==1) text = monthName + text;
+ var div = createElement("div");
+ div.innerHTML = text;
+ return div;
+ };
+
+
+ //**************************************************************************
+ //** createCellFooter
+ //**************************************************************************
+ /** Returns an div empty which is enserted into the cell footer. This method
+ * can be safely overridden to generate custom cell footers.
+ */
+ this.createCellFooter = function(date, i, j){
+ var div = createElement("div");
+ return div;
+ };
+
+
+ //**************************************************************************
+ //** getCells
+ //**************************************************************************
+ /** Returns an array of cells - one for each day in the view. A cell is
+ * defined by a date/id and a bounding rectangle. These cells are used
+ * when dragging events.
+ */
+ this.getCells = function(){
+ var arr = [];
+ for (var id in cells) {
+ if (cells.hasOwnProperty(id)) {
+ var cols = cells[id];
+
+ var r1 = _getRect(cols[0]);
+ var r2 = _getRect(cols[cols.length-1]);
+ var x1 = r1.left;
+ var x2 = r1.right;
+ var y1 = r1.top;
+ var y2 = r2.bottom;
+
+
+ var rect = {
+ left: x1,
+ right: x2,
+ top: y1,
+ bottom: y2,
+ width: x2-x1,
+ height: y2-y1
+ };
+
+ arr.push({
+ id: id,
+ rect: rect
+ });
+ }
+ }
+ return arr;
+ };
+
+
+ //**************************************************************************
+ //** getEventStore
+ //**************************************************************************
+ this.getEventStore = function(){
+ return store;
+ };
+
+
+ //**************************************************************************
+ //** addEvent
+ //**************************************************************************
+ this.addEvent = function(event){
+
+ log("Adding " + event.getSubject() + "...");
+
+
+ //Check if we've already rendered the given event
+ var numDays = event.numDays();
+ if (numDays>=1){
+ for (var id in multiDayCols) {
+ if (multiDayCols.hasOwnProperty(id)) {
+ var arr = multiDayCols[id];
+ if (arr!=null){
+ for (var i=1; i=1){
+ addMultiDayEvent(event);
+ }
+ else{
+ addSingleDayEvent(event);
+ }
+
+
+ //Update the event store
+ store.add(event);
+ };
+
+
+
+ //**************************************************************************
+ //** removeEvent
+ //**************************************************************************
+
+ this.removeEvent = function(event){
+
+
+ //Remove the event from the store
+ store.remove(event);
+
+
+ //Update view
+ if (event.numDays()>=1){ //Multiday Event
+
+
+ //Remove div
+ var rows = [];
+ for (var id in multiDayCols) {
+ if (multiDayCols.hasOwnProperty(id)) {
+ var arr = multiDayCols[id];
+ for (var i=1; i=2){
+ var str = id.split("-");
+ var month = parseInt(str[0]);
+ var day = parseInt(str[1]);
+ var year = parseInt(str[2]);
+ var date = new Date(year, month-1, day);
+ for (var x=1; x1){
+ var td = arr[1];
+ if (td.parentNode!=null){
+ if (td.parentNode.previousSibling==firstRow){
+ var eventDiv = td.firstChild;
+ if (eventDiv!=null){
+ eventDiv.style.marginTop = "0px";
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+ }
+
+
+
+ alignEvents();
+
+ }
+ else{ //Single day event
+
+ var d = event.getStartDate();
+ var id = (d.getMonth()+1) + "-" + d.getDate() + "-" + d.getFullYear();
+ var td = singleDayCols[id];
+ var div = td.childNodes[0];
+ for (var i=0; i=event.getStartDate().getTime()){
+
+ div.insertBefore(wrapper, div.childNodes[i]);
+ addedEvent = true;
+ break;
+ }
+ }
+ }
+ if (!addedEvent){
+ div.appendChild(wrapper);
+ }
+
+
+ //Ensure a consistent gap between stacked events
+ spaceEvents(div);
+
+ };
+
+
+ //**************************************************************************
+ //** addMultiDayEvent
+ //**************************************************************************
+ /** Used to render multi-day events
+ */
+ var addMultiDayEvent = function(event){
+
+
+ //Find cells to span
+ var cols = [];
+ for (var x=0; x0) spans.push(span);
+
+
+ //Iterate through the spans and render events
+ for (var i=0; i1){
+
+ for (k=1; k0 || _col.parentNode==null){
+ spanInUse = true;
+ break;
+ }
+
+ }
+
+ if (!spanInUse){
+ col = cols[0][k];
+ break;
+ }
+ }
+ }
+
+
+ //If a suitable column was not found, add a new row and select a
+ //column from the new row
+ if (col==null){
+
+ //Insert row
+ var currRow = cols[0][cols[0].length-1].parentNode;
+ var nextRow = currRow.nextSibling;
+ var newRow = cols[0][0].parentNode.cloneNode(true);
+ currRow.parentNode.insertBefore(newRow, nextRow);
+
+ //Update the multiDayCols array
+ for (var j=0; j0 && col.previousSibling==null;
+ var continueRight = col.nextSibling==null && (spans.length>1 && i1) wrapper.style.marginTop = (eventSpacing*2) + "px"; //Vertical padding
+ wrapper.appendChild(outerDiv);
+ wrapper.event = event;
+ wrapper.onclick = function(e){
+ var listener = me.getListener('eventclick');
+ if (listener!=null){
+ var callback = listener.callback;
+ var scope = listener.scope;
+ callback.apply(scope, [this.event, this, me, e]);
+ }
+ };
+
+
+ col.appendChild(wrapper);
+ col.style.height = eventHeight + "px";
+ mutiDayEventDivs.push(wrapper);
+ }
+
+
+
+ //Move the event divs up to fill the gap created by multi-events
+ alignEvents();
+ };
+
+
+ //**************************************************************************
+ //** alignEvents
+ //**************************************************************************
+ /** Used to vertically align events to fill any gaps created by multi-events.
+ * If there are multiday events that span a cell, events are aligned with
+ * the last multiday event in the cell. Otherwise, events are aligned with
+ * the cell header
+ */
+ var alignEvents = function(){
+ for (var id in singleDayCols) {
+ if (singleDayCols.hasOwnProperty(id)) {
+ var td = singleDayCols[id];
+ var div = td.childNodes[0];
+ var cols = multiDayCols[id];
+ var offset = 0;
+
+
+
+ var numUsedCols = 0;
+ var lastUsedCol = null;
+ for (var i=1; i0 || cols[i].parentNode==null){
+ numUsedCols++;
+ lastUsedCol = i;
+ }
+ }
+
+
+ //The following logic doesn't work for FireFox:
+ //var offset = (((cols.length-1)-numUsedCols)*(eventHeight));
+ //if (numUsedCols==0) offset+=eventSpacing;
+ //As a workaround, we need to compute offset using the DOM
+ var tr = td.parentNode;
+ var y2 = _getRect(tr).top;
+ var y1;
+ if (lastUsedCol!=null){
+ var lastUsedRow = tr.parentNode.childNodes[lastUsedCol];
+ y1 = _getRect(lastUsedRow).bottom;
+ offset = y2-y1;
+ }
+ else{
+ y1 = _getRect(tr.parentNode.childNodes[0]).top;
+ offset = ((y2-y1)+((eventSpacing*2)-1)); //-1px for spacer row?
+ }
+
+
+ div.style.marginTop = -offset + "px";
+
+
+ //Keep single-day events clear of the multiday bar and each other
+ spaceEvents(div);
+ }
+ }
+ };
+
+
+ //**************************************************************************
+ //** spaceEvents
+ //**************************************************************************
+ /** Ensures a small, consistent vertical gap between the events stacked in a
+ * cell. The event divs can render slightly taller than their wrapper (border
+ * + padding), so aligning to the table rows leaves overlaps. Instead we
+ * measure the actual geometry with getRect() and nudge each event's
+ * margin-top so it clears the event above it - including any multiday event
+ * spanning the cell (which the single-day events must sit below).
+ */
+ var eventSpacer = eventSpacing + 1; //desired gap between events (px)
+
+ var nudgeBelow = function(wrapper, aboveRect){
+ var curr = getEventDiv(wrapper);
+ if (curr==null || aboveRect==null) return;
+ var gap = _getRect(curr).top - aboveRect.bottom;
+ if (gap < eventSpacer){
+ var margin = parseInt(wrapper.style.marginTop);
+ if (isNaN(margin)) margin = 0;
+ wrapper.style.marginTop = (margin + (eventSpacer - gap)) + "px";
+ }
+ };
+
+ var spaceEvents = function(container){
+ if (container==null) return;
+
+ //Collect the single-day event wrappers in render order
+ var wrappers = [];
+ for (var i=0; i tdRect.left && mr.left < tdRect.right){ //same column
+ if (boundary==null || mr.bottom > boundary) boundary = mr.bottom;
+ }
+ }
+ if (boundary!=null) nudgeBelow(wrappers[0], {bottom: boundary});
+
+ //Walk top-to-bottom, pushing each event down until it clears the one
+ //above it. Adjusting a wrapper also shifts the ones below it, so we
+ //re-measure on each iteration.
+ for (var i=1; i0){
+ el = el.childNodes[0];
+ if (el.isCalEvent===true) return el;
+ }
+ return null;
+ };
+
+
+ //**************************************************************************
+ //** getDOM
+ //**************************************************************************
+ this.getDOM = function(){
+ return table;
+ };
+
+
+ //**************************************************************************
+ //** next
+ //**************************************************************************
+ this.next = function(){
+ date.setMonth(date.getMonth()+1);
+ computeRange(date);
+ renderTable();
+ loadEvents();
+ };
+
+
+ //**************************************************************************
+ //** back
+ //**************************************************************************
+ this.back = function(){
+ date.setMonth(date.getMonth()-1);
+ computeRange(date);
+ renderTable();
+ loadEvents();
+ };
+
+
+ //**************************************************************************
+ //** setDate
+ //**************************************************************************
+ this.setDate = function(d){
+ if (d==null) d = new Date();
+
+
+ if (date!=null){
+ if (d.getFullYear()==date.getFullYear() && d.getMonth()==date.getMonth()){
+ date = d;
+ return;
+ }
+ }
+
+
+ date = d;
+ computeRange(date);
+ renderTable();
+ loadEvents();
+ };
+
+
+ //**************************************************************************
+ //** getDate
+ //**************************************************************************
+ this.getDate = function(){
+ return date;
+ };
+
+
+ //**************************************************************************
+ //** computeRange
+ //**************************************************************************
+ var computeRange = function(d){
+
+ //Compute number of rows to render. Credit:
+ //http://stackoverflow.com/a/2485172
+ var year = d.getFullYear();
+ var month = d.getMonth()+1;
+ var firstOfMonth = new Date(year, month-1, 1);
+ var lastOfMonth = new Date(year, month, 0);
+ numWeeks = Math.ceil( (firstOfMonth.getDay() + lastOfMonth.getDate()) / 7);
+
+ startDate = new Date(firstOfMonth);
+ startDate.setDate(startDate.getDate()-firstOfMonth.getDay());
+
+ endDate = new Date(lastOfMonth);
+ endDate.setDate(endDate.getDate()+(6-lastOfMonth.getDay()));
+ };
+
+
+ //**************************************************************************
+ //** getDateRange
+ //**************************************************************************
+ /** Returns the start/end dates represented by this view.
+ */
+ this.getDateRange = function(){
+ return {
+ startDate: new Date(startDate),
+ endDate: new Date(endDate)
+ };
+ };
+
+
+ //**************************************************************************
+ //** getTitle
+ //**************************************************************************
+ /** Returns a title for the current view.
+ */
+ this.getTitle = function(){
+ return (config.monthNames[date.getMonth()] + " " + date.getFullYear());
+ };
+
+
+ //**************************************************************************
+ //** loadEvents
+ //**************************************************************************
+ var loadEvents = function(){
+ var events = store.getEvents();
+ for (var i=0; i10){
- holdActive = false;
- return;
- }
-
-
-
- holdActive = true;
-
-
-
-
- //Initiate drag
- startDrag({
- clientX: x,
- clientY: y
- });
-
-
- //Add "touchmove" event listener
- if (document.addEventListener) {
- div.addEventListener("touchmove", onTouchMove);
- }
- else if (document.attachEvent) {
- div.attachEvent("ontouchmove", onTouchMove);
- }
-
-
- }, holdDelay);
- };
-
- //End touch (similar to "onmouseup")
- div.ontouchend = function(e) {
-
-
- //Enable scrolling in the view
- view.enableTouch();
-
-
- //If the mouse is released immediately (i.e., a click), before the
- //holdStarter runs, then cancel the holdStarter and do the click
- if (holdStarter) {
- clearTimeout(holdStarter);
-
- //run click-only operation here
- //console.log("Click!");
- var listener = view.getListener('eventclick');
- if (listener!=null){
- var callback = listener.callback;
- var scope = listener.scope;
-
- var touch = e.changedTouches[0];
- var x = touch.pageX;
- var y = touch.pageY;
-
- callback.apply(scope, [div.event, div, view, {
- clientX: x,
- clientY: y
- }]);
- }
- }
-
- //Otherwise, if the mouse was being held, end the hold
- else if (holdActive) {
- holdActive = false;
- moveDiv(div);
-
-
-
- //Remove javaxt-cal-event-drag class from the event div
- var innerDiv = getInnerDiv(div);
- innerDiv.className = innerDiv.className.replace( /(?:^|\s)javaxt-cal-event-drag(?!\S)/g , '' );
-
-
- //Remove z-index
- div.style.zIndex = '';
-
-
- //Remove "touchmove" event listener
- if (document.removeEventListener) {
- div.removeEventListener("touchmove", onTouchMove);
- }
- else if (document.detachEvent) {
- div.detachEvent("ontouchmove", onTouchMove);
- }
-
- }
-
- };
-
-
-
-
-
-
- var onMouseMove = function(e){
- var x = e.clientX;
- var y = e.clientY;
- div.style.left = (x-div.xOffset) + 'px';
- div.style.top = (y-div.yOffset) + 'px';
- };
-
- var onTouchMove = function(e) {
- e.preventDefault();
- var touch = e.touches[0];
- var x = touch.pageX;
- var y = touch.pageY;
-
- onMouseMove({
- clientX: x,
- clientY: y
- });
- };
-
-
- var startDrag = function(e){
- var x = e.clientX;
- var y = e.clientY;
-
- var rect = _getRect(div);
- var top = rect.top-parseInt(div.style.marginTop);
-
- var xOffset = x-rect.left;
- var yOffset = view.hasHours() ? (rect.height/2) : y-top;
-
-
- var parentNode = div.parentNode;
-
- var orgStyle = {
- width: div.style.width,
- left: div.style.left,
- top: div.style.top,
- rect: rect
- };
- div.orgStyle = orgStyle;
- div.orgParent = parentNode;
- div.xOffset = xOffset;
- div.yOffset = yOffset;
-
-
- //Disable text selection in the entire document - very important!
- var body = document.getElementsByTagName('body')[0];
- if (!body.className.match(/(?:^|\s)javaxt-noselect(?!\S)/) ){
- body.className += (body.className.length==0 ? "" : " ") + "javaxt-noselect";
- }
-
-
- //Remove div from the current cell and append it to the body
- var nextSibling = div.nextSibling;
- parentNode.removeChild(div);
- body.appendChild(div);
-
-
- //Add placeholder div as needed (e.g. month view)
- if (view.hasHours()==false && nextSibling!=null){
- var placeHolderDiv = document.createElement("div");
- placeHolderDiv.style.width = "100%";
- placeHolderDiv.style.height = rect.height + "px";
- placeHolderDiv.style.marginTop = div.style.marginTop;
- parentNode.insertBefore(placeHolderDiv, nextSibling);
- placeHolderDiv.event = div.event;
- }
-
-
-
- div.style.position = "absolute";
- div.style.width = rect.width + 'px';
- div.style.left = rect.left + 'px';
- div.style.top = (y-yOffset) + 'px';
- div.style.cursor = 'move';
- div.style.zIndex = getNextHighestZindex();
-
-
-
- //Add javaxt-cal-event-drag class to the event div
- var innerDiv = getInnerDiv(div);
- innerDiv.className += " javaxt-cal-event-drag";
- };
-
-
-
-
- /** Used to move an event from cell to cell. */
- var moveDiv = function(div){
-
-
- //Compute geometry of the div
- var rect = _getRect(div);
- var minY = rect.top;
-
-
- //Generate list of cells that intersect the four corners of the div
- var cells = getCells(rect);
-
-
- //If the div doesn't intersect any cells in the view, return the div
- //to its original location.
- if (cells.length==0){
- returnDiv(div);
- return;
- }
-
-
- //Select cell to move to
- var cell;
- if (cells.length==1){
- cell = cells[0];
- }
- else{
-
- //Find the cell with the largest area of intersection
- var intersections = {};
- var keys = [];
- for (var i=0; i0){
- var firstChild = el.childNodes[0];
-
- if (firstChild.className.match(/(?:^|\s)javaxt-cal-event(?!\S)/) ){
- return firstChild;
- }
- else{
- el = firstChild;
- }
- }
- };
-
-
- /** Returns a list of cells that intersect the four corners of a given rectangle. */
- var getCells = function(rect){
-
- var minX = rect.left;
- var maxX = rect.right;
- var minY = rect.top;
- var maxY = rect.bottom;
-
- //Generate list of cells that intersect the four corners of the div
- var cells = [];
- var addCell = function(cell){
- if (cell==null) return;
- for (var i=0; i=minX && x<=maxX){
- var minY = rect.top;
- var maxY = rect.bottom;
- if (y>=minY && y<=maxY){
- return cell;
- }
- }
- }
- return null;
- };
-
-
-
- /** Returns the total area that a given rectangle intersects a cell. */
- var getAreaOfIntersection = function(r1, cell){
-
- var rect = cell.rect;
- var minX = rect.left;
- var maxX = rect.right;
- var minY = rect.top;
- var maxY = rect.bottom;
-
- var left = r1.left;
- var right = r1.right;
- var top = r1.top;
- var bottom = r1.bottom;
-
- if (leftmaxX) right=maxX;
- if (topmaxY) bottom=maxY;
-
- var w = right-left;
- var h = bottom-top;
- return w*h;
- };
-
-
-
-
- /** Returns the time represented by a given y coordinate in the view.
- * The returned value is a decimal (e.g. 9.5 representing 9:30 AM). */
- var getTime = function(y, div){
-
-
- var rows = [];
- var parentNode = div.orgParent;
- for (var i=0; i=minY && y<=maxY){
- return (i/2);
- }
-
- }
- return null;
- };
-
-
-
- /** Used to update a given start and end date with a given time. The time
- * variable must be a decimal value (e.g. 9.5 representing 9:30 AM). */
- var setTime = function(time, startDate, endDate){
- var diff = time - (startDate.getHours() + (startDate.getMinutes()/60));
- var h = Math.floor(diff);
- var m = Math.abs((diff % 1)*60);
-
- startDate.setHours(startDate.getHours()+h);
- startDate.setMinutes(startDate.getMinutes()+m);
-
- endDate.setHours(endDate.getHours()+h);
- endDate.setMinutes(endDate.getMinutes()+m);
- };
-
-
-
- var _getRect = javaxt.dhtml.calendar.Utils.getRect;
-
-
-
-
- var getNextHighestZindex = function(obj){
- var highestIndex = 0;
- var currentIndex = 0;
- var elArray = Array();
- if(obj){elArray = obj.getElementsByTagName('*');}else{elArray = document.getElementsByTagName('*');}
- for(var i=0; i < elArray.length; i++){
- if (elArray[i].currentStyle){
- currentIndex = parseFloat(elArray[i].currentStyle['zIndex']);
- }else if(window.getComputedStyle){
- currentIndex = parseFloat(document.defaultView.getComputedStyle(elArray[i],null).getPropertyValue('z-index'));
- }
- if(!isNaN(currentIndex) && currentIndex > highestIndex){highestIndex = currentIndex;}
- }
- return(highestIndex+1);
- };
-
- }
+if(!javaxt) var javaxt={};
+if(!javaxt.dhtml) javaxt.dhtml={};
+if(!javaxt.dhtml.calendar) javaxt.dhtml.calendar={};
+javaxt.dhtml.calendar.Utils = {
+
+
+ //**************************************************************************
+ //** getDaysBetween
+ //**************************************************************************
+ /** Returns the number of days between 2 dates. Returns a decimal value.
+ */
+ getDaysBetween : function(startDate, endDate){
+
+ function treatAsUTC(date) {
+ var result = new Date(date);
+ result.setMinutes(result.getMinutes() - result.getTimezoneOffset());
+ return result;
+ }
+
+ var millisecondsPerDay = 24 * 60 * 60 * 1000;
+ return (treatAsUTC(endDate) - treatAsUTC(startDate)) / millisecondsPerDay;
+ },
+
+
+ //**************************************************************************
+ //** getStyle
+ //**************************************************************************
+ /** Used to get or create a style object for a given event
+ */
+ getStyle: function(event, config){
+ var style = event.getStyle();
+ if (!style) style = {};
+ if (!style.event) style.event = config.style.event;
+ if (!style.eventContinueLeft) style.eventContinueLeft = config.style.eventContinueLeft;
+ if (!style.eventContinueRight) style.eventContinueRight = config.style.eventContinueRight;
+ return style;
+ },
+
+
+ //**************************************************************************
+ //** addColSpan
+ //**************************************************************************
+ /** Used to add a colspan to a given column.
+ */
+ addColSpan : function(td, newSpan){
+ if (newSpan<2) return;
+
+ //Add colspan
+ td.colSpan = newSpan;
+
+
+
+ var deleteCells = function(startCol, numCells){
+ if (startCol==null) return;
+ var td = startCol;
+ var tr = td.parentNode;
+ var x = 0;
+ var del = [];
+ while (x10){
+ holdActive = false;
+ return;
+ }
+
+
+
+ holdActive = true;
+
+
+
+
+ //Initiate drag
+ startDrag({
+ clientX: x,
+ clientY: y
+ });
+
+
+ //Add "touchmove" event listener
+ if (document.addEventListener) {
+ div.addEventListener("touchmove", onTouchMove);
+ }
+ else if (document.attachEvent) {
+ div.attachEvent("ontouchmove", onTouchMove);
+ }
+
+
+ }, holdDelay);
+ };
+
+ //End touch (similar to "onmouseup")
+ div.ontouchend = function(e) {
+
+
+ //Enable scrolling in the view
+ view.enableTouch();
+
+
+ //If the mouse is released immediately (i.e., a click), before the
+ //holdStarter runs, then cancel the holdStarter and do the click
+ if (holdStarter) {
+ clearTimeout(holdStarter);
+
+ //run click-only operation here
+ //console.log("Click!");
+ var listener = view.getListener('eventclick');
+ if (listener!=null){
+ var callback = listener.callback;
+ var scope = listener.scope;
+
+ var touch = e.changedTouches[0];
+ var x = touch.pageX;
+ var y = touch.pageY;
+
+ callback.apply(scope, [div.event, div, view, {
+ clientX: x,
+ clientY: y
+ }]);
+ }
+ }
+
+ //Otherwise, if the mouse was being held, end the hold
+ else if (holdActive) {
+ holdActive = false;
+ moveDiv(div);
+
+
+
+ //Remove the "drag" style from the event div
+ var innerDiv = getInnerDiv(div);
+ removeStyle(innerDiv, dragStyle);
+ innerDiv.style.cursor = "pointer";
+
+
+ //Remove z-index
+ div.style.zIndex = '';
+
+
+ //Remove "touchmove" event listener
+ if (document.removeEventListener) {
+ div.removeEventListener("touchmove", onTouchMove);
+ }
+ else if (document.detachEvent) {
+ div.detachEvent("ontouchmove", onTouchMove);
+ }
+
+ }
+
+ };
+
+
+
+
+
+
+ var onMouseMove = function(e){
+ var x = e.clientX;
+ var y = e.clientY;
+ div.style.left = (x-div.xOffset-div.scopeLeft) + 'px';
+ div.style.top = (y-div.yOffset-div.scopeTop) + 'px';
+ };
+
+ var onTouchMove = function(e) {
+ e.preventDefault();
+ var touch = e.touches[0];
+ var x = touch.pageX;
+ var y = touch.pageY;
+
+ onMouseMove({
+ clientX: x,
+ clientY: y
+ });
+ };
+
+
+ var startDrag = function(e){
+ var x = e.clientX;
+ var y = e.clientY;
+
+ var rect = _getRect(div);
+ var top = rect.top-parseInt(div.style.marginTop);
+
+ var xOffset = x-rect.left;
+ var yOffset = view.hasHours() ? (rect.height/2) : y-top;
+
+
+ var parentNode = div.parentNode;
+
+ var orgStyle = {
+ width: div.style.width,
+ left: div.style.left,
+ top: div.style.top,
+ rect: rect
+ };
+ div.orgStyle = orgStyle;
+ div.orgParent = parentNode;
+ div.xOffset = xOffset;
+ div.yOffset = yOffset;
+
+
+ //Find the calendar container to drag within. Dragging within the
+ //container (instead of document.body) keeps any scoped styles (e.g.
+ //".javaxt-calendar .javaxt-cal-event") applied to the event while it
+ //is being dragged. Positions below are made relative to this element.
+ var scope = parentNode;
+ while (scope && !(scope.className && (""+scope.className).indexOf("javaxt-calendar")>-1)){
+ scope = scope.parentNode;
+ }
+ var scopeRect = scope ? _getRect(scope) : {left: 0, top: 0};
+ if (!scope) scope = document.getElementsByTagName('body')[0];
+ div.scopeLeft = scopeRect.left;
+ div.scopeTop = scopeRect.top;
+
+
+ //Disable text selection in the entire document - very important!
+ var body = document.getElementsByTagName('body')[0];
+ if (!body.className.match(/(?:^|\s)javaxt-noselect(?!\S)/) ){
+ body.className += (body.className.length==0 ? "" : " ") + "javaxt-noselect";
+ }
+
+
+ //Remove div from the current cell and append it to the calendar scope
+ var nextSibling = div.nextSibling;
+ parentNode.removeChild(div);
+ scope.appendChild(div);
+
+
+ //Add placeholder div as needed (e.g. month view)
+ if (view.hasHours()==false && nextSibling!=null){
+ var placeHolderDiv = createElement("div", {
+ width: "100%",
+ height: rect.height + "px",
+ marginTop: div.style.marginTop
+ });
+ parentNode.insertBefore(placeHolderDiv, nextSibling);
+ placeHolderDiv.event = div.event;
+ }
+
+
+
+ div.style.position = "absolute";
+ div.style.width = rect.width + 'px';
+ div.style.left = (rect.left-div.scopeLeft) + 'px';
+ div.style.top = (y-yOffset-div.scopeTop) + 'px';
+ div.style.cursor = 'move';
+ div.style.zIndex = getNextHighestZindex();
+
+
+
+ //Apply the "drag" style to the event div
+ var innerDiv = getInnerDiv(div);
+ addStyle(innerDiv, dragStyle);
+ };
+
+
+
+
+ /** Used to move an event from cell to cell. */
+ var moveDiv = function(div){
+
+
+ //Compute geometry of the div
+ var rect = _getRect(div);
+ var minY = rect.top;
+
+
+ //Generate list of cells that intersect the four corners of the div
+ var cells = getCells(rect);
+
+
+ //If the div doesn't intersect any cells in the view, return the div
+ //to its original location.
+ if (cells.length==0){
+ returnDiv(div);
+ return;
+ }
+
+
+ //Select cell to move to
+ var cell;
+ if (cells.length==1){
+ cell = cells[0];
+ }
+ else{
+
+ //Find the cell with the largest area of intersection
+ var intersections = {};
+ var keys = [];
+ for (var i=0; i0){
+ var firstChild = el.childNodes[0];
+
+ if (firstChild.isCalEvent===true){
+ return firstChild;
+ }
+ else{
+ el = firstChild;
+ }
+ }
+ };
+
+
+ /** Returns a list of cells that intersect the four corners of a given rectangle. */
+ var getCells = function(rect){
+
+ var minX = rect.left;
+ var maxX = rect.right;
+ var minY = rect.top;
+ var maxY = rect.bottom;
+
+ //Generate list of cells that intersect the four corners of the div
+ var cells = [];
+ var addCell = function(cell){
+ if (cell==null) return;
+ for (var i=0; i=minX && x<=maxX){
+ var minY = rect.top;
+ var maxY = rect.bottom;
+ if (y>=minY && y<=maxY){
+ return cell;
+ }
+ }
+ }
+ return null;
+ };
+
+
+
+
+
+ /** Returns the time represented by a given y coordinate in the view.
+ * The returned value is a decimal (e.g. 9.5 representing 9:30 AM). */
+ var getTime = function(y, div){
+
+
+ var rows = [];
+ var parentNode = div.orgParent;
+ for (var i=0; i=minY && y<=maxY){
+ return (i/2);
+ }
+
+ }
+ return null;
+ };
+
+
+
+ /** Used to update a given start and end date with a given time. The time
+ * variable must be a decimal value (e.g. 9.5 representing 9:30 AM). */
+ var setTime = function(time, startDate, endDate){
+ var diff = time - (startDate.getHours() + (startDate.getMinutes()/60));
+ var h = Math.floor(diff);
+ var m = Math.abs((diff % 1)*60);
+
+ startDate.setHours(startDate.getHours()+h);
+ startDate.setMinutes(startDate.getMinutes()+m);
+
+ endDate.setHours(endDate.getHours()+h);
+ endDate.setMinutes(endDate.getMinutes()+m);
+ };
+
+
+
+ var _getRect = javaxt.dhtml.utils.getRect;
+ var getNextHighestZindex = javaxt.dhtml.utils.getNextHighestZindex;
+ var getAreaOfIntersection = javaxt.dhtml.utils.getAreaOfIntersection;
+
+ }
};
\ No newline at end of file
diff --git a/src/callout/Callout.js b/src/callout/Callout.js
index 3fd5aeb..54081d8 100644
--- a/src/callout/Callout.js
+++ b/src/callout/Callout.js
@@ -1,531 +1,531 @@
-if(!javaxt) var javaxt={};
-if(!javaxt.dhtml) javaxt.dhtml={};
-
-
-//******************************************************************************
-//** Callout Class
-//******************************************************************************
-/**
- * Used to create simple tooltip/popup boxes with an arrow.
- *
- ******************************************************************************/
-
-javaxt.dhtml.Callout = function(parent, config) {
- this.className = "javaxt.dhtml.Callout";
-
- var me = this;
- var div, innerDiv, callout, notch, notchBorder;
- var opening = false;
-
- var defaultConfig = {
-
- position: "absolute",
-
-
- /** Style for individual elements within the component. Note that you can
- * provide CSS class names instead of individual style definitions.
- */
- style: {
-
- panel: {
- border: "1px solid #c5d9e8",
- backgroundColor: "#eef4f9",
- borderRadius: "6px",
- boxShadow: "0 12px 14px 0 rgba(0, 0, 0, 0.2), 0 13px 20px 0 rgba(0, 0, 0, 0.2)"
- },
-
- arrow: {
-
- //Only backgroundColor, borderColor, width, height, and padding
- //are considered. All other properties are ignored.
- borderColor: "#c5d9e8",
- backgroundColor: "#eef4f9",
- width: "10px",
- height: "10px",
- padding: "10px"
- }
-
- }
- };
-
-
- //**************************************************************************
- //** Constructor
- //**************************************************************************
- var init = function(){
-
- //Clone the config so we don't modify the original config object
- var clone = {};
- merge(clone, config);
-
-
- //Merge clone with default config
- merge(clone, defaultConfig);
- config = clone;
-
-
-
-
- //Create outer div
- div = createElement("div", parent);
- div.setAttribute("desc", me.className);
- if (config.position==="absolute"){
- div.style.display = "none";
- div.style.position = "absolute";
- div.style.top = div.style.left = 0;
- }
- me.el = div;
-
-
- //Create callout box
- callout = createElement("div", div, config.style.panel);
- callout.style.position = "relative";
- callout.style.margin = 0;
- callout.style.padding = 0;
- callout.style.borderWidth = "1px"; //notch assumes the border is 1px. See showAt() method...
-
-
-
- //Create content div
- innerDiv = createElement("div", callout, {
- width: "100%",
- height: "100%"
- });
-
-
-
- //Create temporary div to get arrow style
- var temp = createElement("div", config.style.arrow);
- temp.style.position = "absolute";
- temp.style.visibility = 'hidden';
- temp.style.display = 'block';
- var body = document.getElementsByTagName("body")[0];
- body.appendChild(temp);
- var style = temp.currentStyle || window.getComputedStyle(temp);
- var getStyle = function(prop){
-
- var _getStyle = function(prop){
- if (style.getPropertyValue){
- var val = style.getPropertyValue(prop);
- if (val && val.length>0) return val;
- prop = prop.replace( /([a-z])([A-Z])/g, '$1-$2' ).toLowerCase();
- return style.getPropertyValue(prop);
- }
- else{
- return style[prop];
- }
- };
-
- if (prop instanceof Array){
- var arr = prop;
- for (var i=0; i0){
- return val;
- }
- }
- }
- else{
- return _getStyle(prop);
- }
-
- };
-
- config.arrow = {
- backgroundColor: getStyle("backgroundColor"),
- borderColor: getStyle(["borderColor", "borderLeftColor", "borderRightColor", "borderTopColor", "borderBottomColor"]),
- paddingTop: parseInt(getStyle("paddingTop")),
- paddingBottom: parseInt(getStyle("paddingBottom")),
- paddingLeft: parseInt(getStyle("paddingLeft")),
- paddingRight: parseInt(getStyle("paddingRight"))
- };
- temp.style.border = 0;
- temp.style.padding = 0;
- temp.style.margin = 0;
- config.arrow.width = temp.offsetWidth;
- config.arrow.height = temp.offsetHeight;
- body.removeChild(temp);
- temp = null;
-
-
-
- //Create notch (triangle)
- notch = createElement("b");
- notch.setAttribute("desc","notch");
- notch.style.position = "absolute";
- notch.style.top=0;
- notch.style.left=0;
- notch.style.margin=0;
- notch.style.padding=0;
- notch.style.width=0;
- notch.style.height=0;
- notch.style.fontSize=0;
- notch.style.lineHeight=0;
-
-
-
- //Create border for the notch
- notchBorder = createElement("b", div);
- notchBorder.setAttribute("desc","notchBorder");
- notchBorder.style.position="absolute";
- notchBorder.style.top=0;
- notchBorder.style.left=0;
- notchBorder.style.margin=0;
- notchBorder.style.padding=0;
- notchBorder.style.width=0;
- notchBorder.style.height=0;
- notchBorder.style.fontSize=0;
- notchBorder.style.lineHeight=0;
-
-
- // ie6 transparent fix
- //_border-right-color: pink;
- //_border-left-color: pink;
- //_filter: chroma(color=pink);
-
-
-
- div.appendChild(notch);
-
-
-
- //Add event listeners
- if (config.position==="absolute"){
-
- var onresize = function(){
- me.hide();
- };
-
- var onclick = function(e){
- var x = e.clientX;
- var y = e.clientY;
- hideIfOutside(x, y);
- };
-
- var ontouchstart = function(e){
- var x = e.changedTouches[0].pageX;
- var y = e.changedTouches[0].pageY;
- hideIfOutside(x, y);
- };
-
-
- if (document.addEventListener) { // For all major browsers, except IE 8 and earlier
- document.addEventListener("click", onclick);
- document.addEventListener("touchstart", ontouchstart);
- window.addEventListener("resize", onresize);
- }
- else if (document.attachEvent) { // For IE 8 and earlier versions
- document.attachEvent("onclick", onclick);
- document.attachEvent("ontouchstart", ontouchstart);
- window.attachEvent("onresize", onresize);
- }
- }
- };
-
-
- //**************************************************************************
- //** hideIfOutside
- //**************************************************************************
- var hideIfOutside = function(x, y){
-
- if (opening) return;
-
- if (div.style.display === 'block'){
-
- var x1 = parseInt(div.style.left);
- var x2 = x1+div.offsetWidth;
- var y1 = parseInt(div.style.top);
- var y2 = y1+div.offsetHeight;
-
- if (xx2){
- me.hide();
- }
- else{
- if (yy2){
- me.hide();
- }
- }
- }
- };
-
-
- //**************************************************************************
- //** getInnerDiv
- //**************************************************************************
- /** Returns the content div inside the callout that can be populated with
- * text, html, menu buttons, etc.
- */
- this.getInnerDiv = function(){
- return innerDiv;
- };
-
-
- //**************************************************************************
- //** getSize
- //**************************************************************************
- /** Returns the width and height of the callout.
- */
- this.getSize = function(){
- var size;
-
- if (div.style.display === 'none'){
- div.style.visibility = 'hidden';
- div.style.display = 'block';
- size = {
- width: div.offsetWidth,
- height: div.offsetHeight
- };
- div.style.visibility = '';
- div.style.display = 'none';
- }
- else{
- size = {
- width: div.offsetWidth,
- height: div.offsetHeight
- };
- }
- return size;
- };
-
-
- //**************************************************************************
- //** show
- //**************************************************************************
- /** Used to render the callout.
- */
- this.show = function(){
- opening = true;
-
- div.style.zIndex = getNextHighestZindex();
- div.style.display = 'block';
- me.onShow();
-
- setTimeout(function() {
- opening = false;
- }, 500);
- };
-
-
- //**************************************************************************
- //** showAt
- //**************************************************************************
- /** Used to render the callout at a specific coordinate. The tip of the
- * arrow associated with the callout will appear at the given coordinate.
- *
- * @param position Where to place the callout box relative to the given
- * coordinate. Options include left, right, above, and below.
- *
- * @param align Options include left, right, center if the "position" is
- * above or below. Otherwise, options are top, bottom, or middle.
- */
- this.showAt = function(x, y, position, align){
- opening = true;
-
-
- //Hack to get div width/height BEFORE making the div visible
- div.style.visibility = 'hidden';
- div.style.display = 'block';
-
-
- var backgroundColor = config.arrow.backgroundColor;
- var borderColor = config.arrow.borderColor;
-
-
- var notchSize = Math.max(config.arrow.width, config.arrow.height);
- var notchOffset = 0;
- var notchCenter = notchSize;
- var notchHeight = notchSize;
-
-
-
- var halign = function(){
- if (align==="left"){
- notchOffset = config.arrow.paddingLeft;
- div.style.left = (x-(notchOffset+notchCenter)) + "px";
- notch.style.left=notchBorder.style.left=notchOffset + "px";
- }
- else if (align==="right"){
- notchOffset = config.arrow.paddingRight;
- div.style.left = ((x-div.offsetWidth) + (notchOffset+notchCenter)) + "px";
- notch.style.left=notchBorder.style.left= (div.offsetWidth-(notchOffset+(notchCenter*2))) + "px";
- }
- else if (align==="center" || align==="middle"){
- var center = div.offsetWidth/2;
- div.style.left = (x-center) + "px";
- notch.style.left=notchBorder.style.left= (center-notchCenter) + "px";
- }
- else{
- return;
- }
- };
-
-
- var valign = function(){
- callout.style.top = "0px";
-
- if (align==="top"){
- notchOffset = config.arrow.paddingTop;
- div.style.top = (y-(notchOffset+notchCenter)) + "px";
- notch.style.top=notchBorder.style.top=notchOffset + "px";
- }
- else if (align==="bottom"){
- notchOffset = config.arrow.paddingBottom;
- div.style.top = ((y-div.offsetWidth) + (notchOffset+notchCenter)) + "px";
- notch.style.top = notchBorder.style.top = (div.offsetHeight-(notchOffset+(notchCenter*2))) + "px";
- }
- else if (align==="middle" || align==="center"){
- var center = div.offsetHeight/2;
- div.style.top = (y-center) + "px";
- notch.style.top = notchBorder.style.top = (center-notchCenter) + "px";
- }
- else{
- return;
- }
- };
-
-
- //Update notch style align elements. Notch style is based on a CSS triangle
- //described here: https://css-tricks.com/snippets/css/css-triangle/
- if (position==="above"){
-
- //Update notch style so the arrow is pointing down
- notch.style.borderTop=notchBorder.style.borderTop=notchSize+"px solid " + backgroundColor;
- notch.style.borderLeft=notchBorder.style.borderLeft=notchSize+"px solid transparent";
- notch.style.borderRight=notchBorder.style.borderRight=notchSize+"px solid transparent";
- notch.style.borderBottom=notchBorder.style.borderBottom=0;
- notchBorder.style.borderTopColor=borderColor; //<--Make sure this appears after all other border definitions!
-
-
- //Set vertical position of the notch, div, and callout
- div.style.left = x + "px";
- div.style.top = ((y-div.offsetHeight)-notchHeight) + "px";
- callout.style.top = "0px";
- notch.style.top = (div.offsetHeight-1) + "px"; //-1 for the border width
- notchBorder.style.top = div.offsetHeight + "px";
-
-
- //Set horizontal alignment of the notch and div
- halign();
- }
- else if (position==="below"){
-
- //Update notch style so the arrow is pointing up
- notch.style.borderTop=notchBorder.style.borderTop=0;
- notch.style.borderLeft=notchBorder.style.borderLeft=notchSize+"px solid transparent";
- notch.style.borderRight=notchBorder.style.borderRight=notchSize+"px solid transparent";
- notch.style.borderBottom=notchBorder.style.borderBottom=notchSize+"px solid " + backgroundColor;
- notchBorder.style.borderBottomColor=borderColor; //<--Make sure this appears after all other border definitions!
-
-
- //Set vertical position of the notch, div, and callout
- div.style.left = x + "px";
- div.style.top = y + "px";
- callout.style.top = notchHeight + "px";
- notch.style.top = "1px"; //+1 for border width
- notchBorder.style.top = "0px";
-
-
- //Set horizontal position of the notch and div
- halign();
- }
- else if (position==="left"){
-
- //Update notch style so the arrow is pointing right
- notch.style.borderTop=notchBorder.style.borderTop=notchSize+"px solid transparent";
- notch.style.borderLeft=notchBorder.style.borderLeft=notchSize+"px solid " + backgroundColor;
- notch.style.borderRight=notchBorder.style.borderRight=0;
- notch.style.borderBottom=notchBorder.style.borderBottom=notchSize+"px solid transparent";
- notchBorder.style.borderLeftColor=borderColor; //<--Make sure this appears after all other border definitions!
-
-
- //Set horizontal position
- div.style.left = (x-(div.offsetWidth+notchHeight)) + "px";
- callout.style.left = "0px";
- notch.style.left = (div.offsetWidth-1) + "px";
- notchBorder.style.left = div.offsetWidth + "px";
-
-
- //Set vertical position of the notch and div
- valign();
- }
- else if (position==="right"){
-
-
- //Update notch style so the arrow is pointing left
- notch.style.borderTop=notchBorder.style.borderTop=notchSize+"px solid transparent";
- notch.style.borderLeft=notchBorder.style.borderLeft=0;
- notch.style.borderRight=notchBorder.style.borderRight=notchSize+"px solid " + backgroundColor;
- notch.style.borderBottom=notchBorder.style.borderBottom=notchSize+"px solid transparent";
- notchBorder.style.borderRightColor=borderColor; //<--Make sure this appears after all other border definitions!
-
-
- //Set horizontal position
- div.style.left = x + "px";
- callout.style.left = notchHeight + "px";
- notch.style.left = "1px";
- notchBorder.style.left = "0px";
-
-
- //Set vertical position of the notch and div
- valign();
- }
- else{
- return;
- }
-
-
- div.style.visibility = "";
-
- me.show();
- };
-
-
- //**************************************************************************
- //** hide
- //**************************************************************************
- /** Used to hide the callout.
- */
- this.hide = function(){
- div.style.display = 'none';
- opening = false;
- me.onHide();
- };
-
-
- //**************************************************************************
- //** isVisible
- //**************************************************************************
- /** Returns true of the callout is visible.
- */
- this.isVisible = function(){
- return (div.style.display !== 'none');
- };
-
-
- //**************************************************************************
- //** onShow
- //**************************************************************************
- /** Called whenever the callout is made visible.
- */
- this.onShow = function(){};
-
-
- //**************************************************************************
- //** onHide
- //**************************************************************************
- /** Called whenever the callout is hidden.
- */
- this.onHide = function(){};
-
-
-
- //**************************************************************************
- //** Utils
- //**************************************************************************
- var merge = javaxt.dhtml.utils.merge;
- var getNextHighestZindex = javaxt.dhtml.utils.getNextHighestZindex;
- var createElement = javaxt.dhtml.utils.createElement;
-
-
- init();
+if(!javaxt) var javaxt={};
+if(!javaxt.dhtml) javaxt.dhtml={};
+
+
+//******************************************************************************
+//** Callout Class
+//******************************************************************************
+/**
+ * Used to create simple tooltip/popup boxes with an arrow.
+ *
+ ******************************************************************************/
+
+javaxt.dhtml.Callout = function(parent, config) {
+ this.className = "javaxt.dhtml.Callout";
+
+ var me = this;
+ var div, innerDiv, callout, notch, notchBorder;
+ var opening = false;
+
+ var defaultConfig = {
+
+ position: "absolute",
+
+
+ /** Style for individual elements within the component. Note that you can
+ * provide CSS class names instead of individual style definitions.
+ */
+ style: {
+
+ panel: {
+ border: "1px solid #c5d9e8",
+ backgroundColor: "#eef4f9",
+ borderRadius: "6px",
+ boxShadow: "0 12px 14px 0 rgba(0, 0, 0, 0.2), 0 13px 20px 0 rgba(0, 0, 0, 0.2)"
+ },
+
+ arrow: {
+
+ //Only backgroundColor, borderColor, width, height, and padding
+ //are considered. All other properties are ignored.
+ borderColor: "#c5d9e8",
+ backgroundColor: "#eef4f9",
+ width: "10px",
+ height: "10px",
+ padding: "10px"
+ }
+
+ }
+ };
+
+
+ //**************************************************************************
+ //** Constructor
+ //**************************************************************************
+ var init = function(){
+
+ //Clone the config so we don't modify the original config object
+ var clone = {};
+ merge(clone, config);
+
+
+ //Merge clone with default config
+ merge(clone, defaultConfig);
+ config = clone;
+
+
+
+
+ //Create outer div
+ div = createElement("div", parent, "javaxt-callout");
+ if (config.position==="absolute"){
+ div.style.display = "none";
+ div.style.position = "absolute";
+ div.style.top = div.style.left = 0;
+ }
+ me.el = div;
+
+
+ //Create callout box
+ callout = createElement("div", div, config.style.panel);
+ callout.style.position = "relative";
+ callout.style.margin = 0;
+ callout.style.padding = 0;
+ callout.style.borderWidth = "1px"; //notch assumes the border is 1px. See showAt() method...
+
+
+
+ //Create content div
+ innerDiv = createElement("div", callout, {
+ width: "100%",
+ height: "100%"
+ });
+
+
+
+ //Create temporary div to get arrow style
+ var temp = createElement("div", "javaxt-callout");
+ temp.style.position = "absolute";
+ temp.style.visibility = 'hidden';
+ temp.style.display = 'block';
+ var body = document.getElementsByTagName("body")[0];
+ body.appendChild(temp);
+ temp = createElement("div", temp, config.style.arrow);
+ var style = temp.currentStyle || window.getComputedStyle(temp);
+ var getStyle = function(prop){
+
+ var _getStyle = function(prop){
+ if (style.getPropertyValue){
+ var val = style.getPropertyValue(prop);
+ if (val && val.length>0) return val;
+ prop = prop.replace( /([a-z])([A-Z])/g, '$1-$2' ).toLowerCase();
+ return style.getPropertyValue(prop);
+ }
+ else{
+ return style[prop];
+ }
+ };
+
+ if (prop instanceof Array){
+ var arr = prop;
+ for (var i=0; i0){
+ return val;
+ }
+ }
+ }
+ else{
+ return _getStyle(prop);
+ }
+
+ };
+
+ config.arrow = {
+ backgroundColor: getStyle("backgroundColor"),
+ borderColor: getStyle(["borderColor", "borderLeftColor", "borderRightColor", "borderTopColor", "borderBottomColor"]),
+ paddingTop: parseInt(getStyle("paddingTop")),
+ paddingBottom: parseInt(getStyle("paddingBottom")),
+ paddingLeft: parseInt(getStyle("paddingLeft")),
+ paddingRight: parseInt(getStyle("paddingRight"))
+ };
+ temp.style.border = 0;
+ temp.style.padding = 0;
+ temp.style.margin = 0;
+ config.arrow.width = temp.offsetWidth;
+ config.arrow.height = temp.offsetHeight;
+ body.removeChild(temp.parentNode);
+ temp = null;
+
+
+
+ //Create notch (triangle)
+ notch = createElement("b");
+ notch.setAttribute("desc","notch");
+ notch.style.position = "absolute";
+ notch.style.top=0;
+ notch.style.left=0;
+ notch.style.margin=0;
+ notch.style.padding=0;
+ notch.style.width=0;
+ notch.style.height=0;
+ notch.style.fontSize=0;
+ notch.style.lineHeight=0;
+
+
+
+ //Create border for the notch
+ notchBorder = createElement("b", div);
+ notchBorder.setAttribute("desc","notchBorder");
+ notchBorder.style.position="absolute";
+ notchBorder.style.top=0;
+ notchBorder.style.left=0;
+ notchBorder.style.margin=0;
+ notchBorder.style.padding=0;
+ notchBorder.style.width=0;
+ notchBorder.style.height=0;
+ notchBorder.style.fontSize=0;
+ notchBorder.style.lineHeight=0;
+
+
+ // ie6 transparent fix
+ //_border-right-color: pink;
+ //_border-left-color: pink;
+ //_filter: chroma(color=pink);
+
+
+
+ div.appendChild(notch);
+
+
+
+ //Add event listeners
+ if (config.position==="absolute"){
+
+ var onresize = function(){
+ me.hide();
+ };
+
+ var onclick = function(e){
+ var x = e.clientX;
+ var y = e.clientY;
+ hideIfOutside(x, y);
+ };
+
+ var ontouchstart = function(e){
+ var x = e.changedTouches[0].pageX;
+ var y = e.changedTouches[0].pageY;
+ hideIfOutside(x, y);
+ };
+
+
+ if (document.addEventListener) { // For all major browsers, except IE 8 and earlier
+ document.addEventListener("click", onclick);
+ document.addEventListener("touchstart", ontouchstart);
+ window.addEventListener("resize", onresize);
+ }
+ else if (document.attachEvent) { // For IE 8 and earlier versions
+ document.attachEvent("onclick", onclick);
+ document.attachEvent("ontouchstart", ontouchstart);
+ window.attachEvent("onresize", onresize);
+ }
+ }
+ };
+
+
+ //**************************************************************************
+ //** hideIfOutside
+ //**************************************************************************
+ var hideIfOutside = function(x, y){
+
+ if (opening) return;
+
+ if (div.style.display === 'block'){
+
+ var x1 = parseInt(div.style.left);
+ var x2 = x1+div.offsetWidth;
+ var y1 = parseInt(div.style.top);
+ var y2 = y1+div.offsetHeight;
+
+ if (xx2){
+ me.hide();
+ }
+ else{
+ if (yy2){
+ me.hide();
+ }
+ }
+ }
+ };
+
+
+ //**************************************************************************
+ //** getInnerDiv
+ //**************************************************************************
+ /** Returns the content div inside the callout that can be populated with
+ * text, html, menu buttons, etc.
+ */
+ this.getInnerDiv = function(){
+ return innerDiv;
+ };
+
+
+ //**************************************************************************
+ //** getSize
+ //**************************************************************************
+ /** Returns the width and height of the callout.
+ */
+ this.getSize = function(){
+ var size;
+
+ if (div.style.display === 'none'){
+ div.style.visibility = 'hidden';
+ div.style.display = 'block';
+ size = {
+ width: div.offsetWidth,
+ height: div.offsetHeight
+ };
+ div.style.visibility = '';
+ div.style.display = 'none';
+ }
+ else{
+ size = {
+ width: div.offsetWidth,
+ height: div.offsetHeight
+ };
+ }
+ return size;
+ };
+
+
+ //**************************************************************************
+ //** show
+ //**************************************************************************
+ /** Used to render the callout.
+ */
+ this.show = function(){
+ opening = true;
+
+ div.style.zIndex = getNextHighestZindex();
+ div.style.display = 'block';
+ me.onShow();
+
+ setTimeout(function() {
+ opening = false;
+ }, 500);
+ };
+
+
+ //**************************************************************************
+ //** showAt
+ //**************************************************************************
+ /** Used to render the callout at a specific coordinate. The tip of the
+ * arrow associated with the callout will appear at the given coordinate.
+ *
+ * @param position Where to place the callout box relative to the given
+ * coordinate. Options include left, right, above, and below.
+ *
+ * @param align Options include left, right, center if the "position" is
+ * above or below. Otherwise, options are top, bottom, or middle.
+ */
+ this.showAt = function(x, y, position, align){
+ opening = true;
+
+
+ //Hack to get div width/height BEFORE making the div visible
+ div.style.visibility = 'hidden';
+ div.style.display = 'block';
+
+
+ var backgroundColor = config.arrow.backgroundColor;
+ var borderColor = config.arrow.borderColor;
+
+
+ var notchSize = Math.max(config.arrow.width, config.arrow.height);
+ var notchOffset = 0;
+ var notchCenter = notchSize;
+ var notchHeight = notchSize;
+
+
+
+ var halign = function(){
+ if (align==="left"){
+ notchOffset = config.arrow.paddingLeft;
+ div.style.left = (x-(notchOffset+notchCenter)) + "px";
+ notch.style.left=notchBorder.style.left=notchOffset + "px";
+ }
+ else if (align==="right"){
+ notchOffset = config.arrow.paddingRight;
+ div.style.left = ((x-div.offsetWidth) + (notchOffset+notchCenter)) + "px";
+ notch.style.left=notchBorder.style.left= (div.offsetWidth-(notchOffset+(notchCenter*2))) + "px";
+ }
+ else if (align==="center" || align==="middle"){
+ var center = div.offsetWidth/2;
+ div.style.left = (x-center) + "px";
+ notch.style.left=notchBorder.style.left= (center-notchCenter) + "px";
+ }
+ else{
+ return;
+ }
+ };
+
+
+ var valign = function(){
+ callout.style.top = "0px";
+
+ if (align==="top"){
+ notchOffset = config.arrow.paddingTop;
+ div.style.top = (y-(notchOffset+notchCenter)) + "px";
+ notch.style.top=notchBorder.style.top=notchOffset + "px";
+ }
+ else if (align==="bottom"){
+ notchOffset = config.arrow.paddingBottom;
+ div.style.top = ((y-div.offsetWidth) + (notchOffset+notchCenter)) + "px";
+ notch.style.top = notchBorder.style.top = (div.offsetHeight-(notchOffset+(notchCenter*2))) + "px";
+ }
+ else if (align==="middle" || align==="center"){
+ var center = div.offsetHeight/2;
+ div.style.top = (y-center) + "px";
+ notch.style.top = notchBorder.style.top = (center-notchCenter) + "px";
+ }
+ else{
+ return;
+ }
+ };
+
+
+ //Update notch style align elements. Notch style is based on a CSS triangle
+ //described here: https://css-tricks.com/snippets/css/css-triangle/
+ if (position==="above"){
+
+ //Update notch style so the arrow is pointing down
+ notch.style.borderTop=notchBorder.style.borderTop=notchSize+"px solid " + backgroundColor;
+ notch.style.borderLeft=notchBorder.style.borderLeft=notchSize+"px solid transparent";
+ notch.style.borderRight=notchBorder.style.borderRight=notchSize+"px solid transparent";
+ notch.style.borderBottom=notchBorder.style.borderBottom=0;
+ notchBorder.style.borderTopColor=borderColor; //<--Make sure this appears after all other border definitions!
+
+
+ //Set vertical position of the notch, div, and callout
+ div.style.left = x + "px";
+ div.style.top = ((y-div.offsetHeight)-notchHeight) + "px";
+ callout.style.top = "0px";
+ notch.style.top = (div.offsetHeight-1) + "px"; //-1 for the border width
+ notchBorder.style.top = div.offsetHeight + "px";
+
+
+ //Set horizontal alignment of the notch and div
+ halign();
+ }
+ else if (position==="below"){
+
+ //Update notch style so the arrow is pointing up
+ notch.style.borderTop=notchBorder.style.borderTop=0;
+ notch.style.borderLeft=notchBorder.style.borderLeft=notchSize+"px solid transparent";
+ notch.style.borderRight=notchBorder.style.borderRight=notchSize+"px solid transparent";
+ notch.style.borderBottom=notchBorder.style.borderBottom=notchSize+"px solid " + backgroundColor;
+ notchBorder.style.borderBottomColor=borderColor; //<--Make sure this appears after all other border definitions!
+
+
+ //Set vertical position of the notch, div, and callout
+ div.style.left = x + "px";
+ div.style.top = y + "px";
+ callout.style.top = notchHeight + "px";
+ notch.style.top = "1px"; //+1 for border width
+ notchBorder.style.top = "0px";
+
+
+ //Set horizontal position of the notch and div
+ halign();
+ }
+ else if (position==="left"){
+
+ //Update notch style so the arrow is pointing right
+ notch.style.borderTop=notchBorder.style.borderTop=notchSize+"px solid transparent";
+ notch.style.borderLeft=notchBorder.style.borderLeft=notchSize+"px solid " + backgroundColor;
+ notch.style.borderRight=notchBorder.style.borderRight=0;
+ notch.style.borderBottom=notchBorder.style.borderBottom=notchSize+"px solid transparent";
+ notchBorder.style.borderLeftColor=borderColor; //<--Make sure this appears after all other border definitions!
+
+
+ //Set horizontal position
+ div.style.left = (x-(div.offsetWidth+notchHeight)) + "px";
+ callout.style.left = "0px";
+ notch.style.left = (div.offsetWidth-1) + "px";
+ notchBorder.style.left = div.offsetWidth + "px";
+
+
+ //Set vertical position of the notch and div
+ valign();
+ }
+ else if (position==="right"){
+
+
+ //Update notch style so the arrow is pointing left
+ notch.style.borderTop=notchBorder.style.borderTop=notchSize+"px solid transparent";
+ notch.style.borderLeft=notchBorder.style.borderLeft=0;
+ notch.style.borderRight=notchBorder.style.borderRight=notchSize+"px solid " + backgroundColor;
+ notch.style.borderBottom=notchBorder.style.borderBottom=notchSize+"px solid transparent";
+ notchBorder.style.borderRightColor=borderColor; //<--Make sure this appears after all other border definitions!
+
+
+ //Set horizontal position
+ div.style.left = x + "px";
+ callout.style.left = notchHeight + "px";
+ notch.style.left = "1px";
+ notchBorder.style.left = "0px";
+
+
+ //Set vertical position of the notch and div
+ valign();
+ }
+ else{
+ return;
+ }
+
+
+ div.style.visibility = "";
+
+ me.show();
+ };
+
+
+ //**************************************************************************
+ //** hide
+ //**************************************************************************
+ /** Used to hide the callout.
+ */
+ this.hide = function(){
+ div.style.display = 'none';
+ opening = false;
+ me.onHide();
+ };
+
+
+ //**************************************************************************
+ //** isVisible
+ //**************************************************************************
+ /** Returns true of the callout is visible.
+ */
+ this.isVisible = function(){
+ return (div.style.display !== 'none');
+ };
+
+
+ //**************************************************************************
+ //** onShow
+ //**************************************************************************
+ /** Called whenever the callout is made visible.
+ */
+ this.onShow = function(){};
+
+
+ //**************************************************************************
+ //** onHide
+ //**************************************************************************
+ /** Called whenever the callout is hidden.
+ */
+ this.onHide = function(){};
+
+
+
+ //**************************************************************************
+ //** Utils
+ //**************************************************************************
+ var merge = javaxt.dhtml.utils.merge;
+ var getNextHighestZindex = javaxt.dhtml.utils.getNextHighestZindex;
+ var createElement = javaxt.dhtml.utils.createElement;
+
+
+ init();
};
\ No newline at end of file
diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js
index 20b358d..d97ed1e 100644
--- a/src/carousel/Carousel.js
+++ b/src/carousel/Carousel.js
@@ -100,12 +100,18 @@ javaxt.dhtml.Carousel = function(parent, config) {
slideOver: false,
- /** If true, will allow touchscreen users to slide back and forth through
- * the panels using touch gestures.
+ /** If true, will allow users to slide back and forth through the panels
+ * using touch gestures.
*/
drag: true,
+ /** Cursor style when dragging. Only applicable when "drag" is set to
+ * true.
+ */
+ dragCursor: "ew-resize", //grabbing
+
+
/** Currently unused
*/
visiblePanels: 1,
@@ -844,6 +850,24 @@ javaxt.dhtml.Carousel = function(parent, config) {
this.beforeChange = function(currPanel, nextPanel, direction){};
+ //**************************************************************************
+ //** onDragStart
+ //**************************************************************************
+ /** Called when a client begins to drag a panel in the carousel
+ * @param currPanel Content of the active panel
+ */
+ this.onDragStart = function(currPanel){};
+
+
+ //**************************************************************************
+ //** onDragEnd
+ //**************************************************************************
+ /** Called when a client completes a drag event
+ * @param currPanel Content of the active panel
+ */
+ this.onDragEnd = function(currPanel){};
+
+
//**************************************************************************
//** getPanels
//**************************************************************************
@@ -930,13 +954,15 @@ javaxt.dhtml.Carousel = function(parent, config) {
var startX, offsetX;
var prevPanel;
+ var notify = true;
+
//Function called when a drag is initiated
var onDragStart = function(e){
+
startX = e.clientX;
offsetX = parseInt(innerDiv.style.left);
-
//Disable text selection in the entire document - very important!
var body = document.getElementsByTagName('body')[0];
if (!body.className.match(/(?:^|\s)javaxt-noselect(?!\S)/) ){
@@ -945,7 +971,6 @@ javaxt.dhtml.Carousel = function(parent, config) {
prevPanel = currPanel;
- innerDiv.style.cursor = 'move';
};
@@ -957,6 +982,19 @@ javaxt.dhtml.Carousel = function(parent, config) {
//Otherwise, client is sliding to the left.
+ //Fire onDragStart event once we actually have some movement
+ if (notify){
+ if (d!==0){
+ me.onDragStart(currPanel.childNodes[0].childNodes[0]);
+ notify = false;
+ }
+ }
+
+
+ //Update cursor style
+ innerDiv.style.cursor = config.dragCursor;
+
+
var left = offsetX-d;
innerDiv.style.left = left + 'px';
@@ -1031,6 +1069,7 @@ javaxt.dhtml.Carousel = function(parent, config) {
//Function called when the user stops dragging the div
var onDragEnd = function(){
+ notify = true;
var rect = _getRect(outerDiv);
var minX = rect.x;
@@ -1082,10 +1121,17 @@ javaxt.dhtml.Carousel = function(parent, config) {
if (animationSteps<0) animationSteps = -animationSteps;
//console.log(start + "/" + end + " --> move " + (start-end) + "px in " + animationSteps + "ms");
+ var direction = (start-end)>0 ? "next" : "back";
+
+
+
slide(innerDiv, start, end, new Date().getTime(), 100, function(){
currPanel = innerDiv.childNodes[visiblePanel];
+ var nextPanel = currPanel.childNodes[0].childNodes[0];
+ me.onDragEnd(nextPanel);
if (currPanel!=prevPanel){
- me.onChange(currPanel.childNodes[0].childNodes[0], prevPanel.childNodes[0].childNodes[0]);
+ me.beforeChange(prevPanel.childNodes[0].childNodes[0], nextPanel, direction);
+ me.onChange(nextPanel, prevPanel.childNodes[0].childNodes[0]);
}
});
@@ -1108,6 +1154,7 @@ javaxt.dhtml.Carousel = function(parent, config) {
//MouseDown
innerDiv.onmousedown = function(e){
if (sliding) return;
+ if (e.button>0) return;
// Do not take any immediate action - just set the holdStarter
// to wait for the predetermined delay, and then begin a hold
@@ -1162,7 +1209,7 @@ javaxt.dhtml.Carousel = function(parent, config) {
document.detachEvent("onmousemove", onDrag);
document.detachEvent("onmouseup", onMouseUp);
}
- innerDiv.style.cursor = 'pointer';
+ innerDiv.style.cursor = '';
onDragEnd();
//Remove the "javaxt-noselect" class
diff --git a/src/checkbox/Checkbox.js b/src/checkbox/Checkbox.js
index 28bac19..03575f6 100644
--- a/src/checkbox/Checkbox.js
+++ b/src/checkbox/Checkbox.js
@@ -1,426 +1,442 @@
-if(!javaxt) var javaxt={};
-if(!javaxt.dhtml) javaxt.dhtml={};
-
-//******************************************************************************
-//** Checkbox Class
-//******************************************************************************
-/**
- * Form input that can be either checked or unchecked. The input consists of
- * a square box with a checkmark when selected.
- *
- * Here's a simple example of how to instantiate a checkbox using an existing
- * div (DOM element) and a minimal config. See config settings for a full
- * range of options.
-
- var checkbox = new javaxt.dhtml.Checkbox(div, {
- label: "I Agree",
- checked: false
- });
-
- * Once the checkbox is instantiated you can call any of the public methods.
- * You can also add event listeners by overriding any of the public "on" or
- * "before" methods like this:
-
- *
- ******************************************************************************/
-
-javaxt.dhtml.Checkbox = function(parent, config) {
- this.className = "javaxt.dhtml.Checkbox";
-
- var me = this;
- var outerDiv;
- var box, check, mask, label;
-
-
- var defaultConfig = {
-
- /** Label for the checkbox (optional). Accepts either a string or a DOM
- * element.
- */
- label: null,
-
-
- /** Value associated with the checkbox (optional). Note that this is
- * different than the checkbox state. Use the isChecked() method to
- * determine whether the checkbox is checked.
- */
- value: null,
-
-
- /** If true, the checkbox will initially render with a check. Default is
- * false.
- */
- checked: false,
-
-
- /** If true, the component will be disabled when rendered. Default is
- * false.
- */
- disabled: false,
-
-
- /** Style for individual elements within the component. Note that you can
- * provide CSS class names instead of individual style definitions.
- */
- style:{
-
- panel: {
- display: "inline-block",
- position: "relative"
- },
-
- box: {
- width: "13px",
- height: "13px",
- border: "1px solid #cccccc",
- borderRadius: "3px",
- backgroundColor: "#F6F6F6",
- cursor: "pointer",
- margin: "0px",
- color: "#2b2b2b"
- },
-
- label: {
- fontFamily: "helvetica,arial,verdana,sans-serif",
- fontSize: "14px",
- whiteSpace: "nowrap",
- cursor: "pointer",
- padding: "1px 0 0 5px"
- },
-
- check: {
- content: "",
- display: "block",
- width: "3px",
- height: "6px",
- border: "solid #ffffff",
- borderWidth: "0 2px 2px 0",
- transform: "rotate(45deg)",
- margin: "1px 0 0 4px"
- },
-
- select: {
- backgroundColor: "#007FFF",
- border: "1px solid #003EFF",
- color: "#FFFFFF"
- },
-
- disable: {
- backgroundColor: "#ffffff",
- border: "1px solid #ffffff",
- borderRadius: "3px",
- cursor: "pointer",
- opacity: "0.5"
- },
-
- hover: {
- backgroundColor: "#ededed"
- }
- }
- };
-
-
- //**************************************************************************
- //** Constructor
- //**************************************************************************
- var init = function(){
-
- if (typeof parent === "string"){
- parent = document.getElementById(parent);
- }
- if (!parent) return;
-
-
- //Clone the config so we don't modify the original config object
- var clone = {};
- merge(clone, config);
-
-
- //Merge clone with default config
- merge(clone, defaultConfig);
- config = clone;
-
-
- //Create container
- outerDiv = createElement('div', parent, config.style.panel);
- if (config.display){
- console.warn(
- "The 'display' config in the javaxt.dhtml.Checkbox " +
- "class has been deprecated. Use panel style instead");
- outerDiv.style.display = config.display;
- }
- me.el = outerDiv;
- addShowHide(me);
-
-
- //Create checkbox
- if (config.label){
-
- //Create table with 2 columns - one for the checkbox
- //and a column for the checkbox label.
- var table = createTable(outerDiv);
- table.style.fontFamily = "inherit";
- table.style.textAlign = "inherit";
- table.style.color = "inherit";
- var tr = table.addRow();
-
-
- box = createElement('div', tr.addColumn(), config.style.box);
- label = createElement("div", tr.addColumn(), config.style.label);
- if (isElement(config.label)){
- label.appendChild(config.label);
- }
- else{
- label.innerHTML = config.label;
- }
-
- addEventHandlers(table);
- }
- else{
-
- //Create checkbox with no label
- box = createElement('div', outerDiv, config.style.box);
- addEventHandlers(box);
- }
-
-
- //Set button state
- if (config.disabled===true) me.disable();
- if (config.checked===true) me.select();
- };
-
-
- //**************************************************************************
- //** addEventHandlers
- //**************************************************************************
- var addEventHandlers = function(div){
-
- //Disable text selection
- div.unselectable="on";
- div.onselectstart=function(){return false;};
-
-
- div.onmousedown=function(){
- addStyle(box, "select");
- return false;
- };
-
-
- //Create onclick function
- var onclick = function(){
- if (config.sound!=null) config.sound.play();
- me.toggle();
- me.onClick(box.checked);
- };
-
-
- //Create logic to process touch events
- var touchStartTime;
- var touchEndTime;
- var x1, x2, y1, y2;
- var isTouch = false;
-
- div.ontouchstart = function(e) {
- isTouch = true;
-
- e.preventDefault();
- x1 = e.changedTouches[0].pageX;
- y1 = e.changedTouches[0].pageY;
- touchStartTime = new Date().getTime();
- touchEndTime = null;
-
- if (box.checked!==true){
- addStyle(box, "hover");
- }
- };
-
- div.ontouchend = function(e) {
-
- touchEndTime= new Date().getTime();
- x2 = e.changedTouches[0].pageX;
- y2 = e.changedTouches[0].pageY;
-
- var distance = Math.sqrt( (x2-=x1)*x2 + (y2-=y1)*y2 );
- if (distance<0) distance = -distance;
- var duration = touchEndTime - touchStartTime;
-
- if ((duration <= 500 && distance <= 10) || //Quick tap
- (duration > 500 && distance <= 10)) { //Long press
- onclick();
- }
- };
-
-
-
- //Logic to process mouse events
- div.onclick = function(){
- if (!isTouch) onclick();
- };
- div.onmouseover = function(){
-
- if (box.checked!==true){
- addStyle(box, "hover");
- }
-
- };
- div.onmouseout = function(){
-
- if (box.checked!==true){
- setStyle(box, "box");
- }
-
- };
- };
-
-
- //**************************************************************************
- //** onClick
- //**************************************************************************
- /** Called whenever the checkbox is clicked.
- */
- this.onClick = function(checked){};
-
-
- //**************************************************************************
- //** onChange
- //**************************************************************************
- /** Called whenever the checkbox value is changed.
- */
- this.onChange = function(checked){};
-
-
- //**************************************************************************
- //** enable
- //**************************************************************************
- /** Used to enable the checkbox.
- */
- this.enable = function(){
- mask.style.visibility = "hidden";
- };
-
-
- //**************************************************************************
- //** disable
- //**************************************************************************
- /** Used to disable the checkbox.
- */
- this.disable = function(){
-
- if (mask){
- mask.style.visibility = "visible";
- }
- else{
-
- mask = createElement('div', config.style.disable);
- mask.style.position = "absolute";
- mask.style.zIndex = "1";
- mask.style.width = "100%";
- mask.style.height = "100%";
-
- var outerDiv = me.el;
- outerDiv.insertBefore(mask, outerDiv.firstChild);
- }
- };
-
-
- //**************************************************************************
- //** select
- //**************************************************************************
- /** Used to add a check to the checkbox.
- */
- this.select = function(silent){
- if (box.checked===true) return;
- box.checked = true;
- addStyle(box,"select");
-
- if (check){
- check.style.visibility = "visible";
- }
- else{
- check = createElement('div', box, config.style.check);
- }
-
- if (silent===true) return;
- me.onChange(true);
- };
-
-
- //**************************************************************************
- //** deselect
- //**************************************************************************
- /** Used to remove the check from the checkbox.
- */
- this.deselect = function(silent){
- if (box.checked===true){
- box.checked = false;
- setStyle(box,"box");
- check.style.visibility = "hidden";
-
- if (silent===true) return;
- me.onChange(false);
- }
- };
-
-
- //**************************************************************************
- //** toggle
- //**************************************************************************
- /** Used to toggle the checkbox state.
- */
- this.toggle = function(){
- if (box.checked===true){
- me.deselect();
- }
- else{
- me.select();
- }
- };
-
-
- //**************************************************************************
- //** getValue
- //**************************************************************************
- /** Returns the value associated with the checkbox. Note that this is
- * different than the checkbox state. Use the isChecked() method to
- * determine whether the checkbox is checked. The value is defined in
- * the config used to instantiate this class.
- */
- this.getValue = function(){
- if (config.value==null) return me.isChecked();
- return config.value;
- };
-
-
- //**************************************************************************
- //** isChecked
- //**************************************************************************
- /** Returns true if the checkbox is checked.
- */
- this.isChecked = function(){
- return box.checked===true;
- };
-
-
- //**************************************************************************
- //** Utils
- //**************************************************************************
- var createElement = javaxt.dhtml.utils.createElement;
- var createTable = javaxt.dhtml.utils.createTable;
- var addShowHide = javaxt.dhtml.utils.addShowHide;
- var isElement = javaxt.dhtml.utils.isElement;
- var merge = javaxt.dhtml.utils.merge;
-
- var setStyle = function(el, style){
- javaxt.dhtml.utils.setStyle(el, config.style[style]);
- };
- var addStyle = function(el, style){
- javaxt.dhtml.utils.addStyle(el, config.style[style]);
- };
-
-
- init();
+if(!javaxt) var javaxt={};
+if(!javaxt.dhtml) javaxt.dhtml={};
+
+//******************************************************************************
+//** Checkbox Class
+//******************************************************************************
+/**
+ * Form input that can be either checked or unchecked. The input consists of
+ * a square box with a checkmark when selected.
+ *
+ * Here's a simple example of how to instantiate a checkbox using an existing
+ * div (DOM element) and a minimal config. See config settings for a full
+ * range of options.
+
+ var checkbox = new javaxt.dhtml.Checkbox(div, {
+ label: "I Agree",
+ checked: false
+ });
+
+ * Once the checkbox is instantiated you can call any of the public methods.
+ * You can also add event listeners by overriding any of the public "on" or
+ * "before" methods like this:
+
- * Once the window is instantiated you can call any of the public methods.
- * You can also add event listeners by overriding any of the public "on" or
- * "before" methods like this:
-
- *
- ******************************************************************************/
-
-javaxt.dhtml.Window = function(parent, config) {
- this.className = "javaxt.dhtml.Window";
-
- var me = this;
-
- var defaultConfig = {
-
- /** DOM element or string rendered in the header. Note that this config
- * is optional and can be set after the Window has been instantiated.
- * See setTitle().
- */
- title: false,
-
- /** DOM element or string rendered in the body. Note that this config is
- * optional and can be set after the Window has been instantiated. See
- * setBody().
- */
- body: null,
-
- /** DOM element or string rendered in the footer. Note that this config
- * is optional and can be set after the Window has been instantiated.
- * See setFooter().
- */
- footer: null,
-
- /** Buttons to put in the footer. Only rendered if no "footer" config is
- * defined. Example:
-
+ * Once the window is instantiated you can call any of the public methods.
+ * You can also add event listeners by overriding any of the public "on" or
+ * "before" methods like this:
+
+ *
+ ******************************************************************************/
+
+javaxt.dhtml.Window = function(parent, config) {
+ this.className = "javaxt.dhtml.Window";
+
+ var me = this;
+
+ var defaultConfig = {
+
+ /** DOM element or string rendered in the header. Note that this config
+ * is optional and can be set after the Window has been instantiated.
+ * See setTitle().
+ */
+ title: false,
+
+ /** DOM element or string rendered in the body. Note that this config is
+ * optional and can be set after the Window has been instantiated. See
+ * setBody().
+ */
+ body: null,
+
+ /** DOM element or string rendered in the footer. Note that this config
+ * is optional and can be set after the Window has been instantiated.
+ * See setFooter().
+ */
+ footer: null,
+
+ /** Buttons to put in the footer. Only rendered if no "footer" config is
+ * defined. Example:
+