1 /*********************************************************************************
2 * SugarCRM Community Edition is a customer relationship management program developed by
3 * SugarCRM, Inc. Copyright (C) 2004-2012 SugarCRM Inc.
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU Affero General Public License version 3 as published by the
7 * Free Software Foundation with the addition of the following permission added
8 * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK
9 * IN WHICH THE COPYRIGHT IS OWNED BY SUGARCRM, SUGARCRM DISCLAIMS THE WARRANTY
10 * OF NON INFRINGEMENT OF THIRD PARTY RIGHTS.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
14 * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
17 * You should have received a copy of the GNU Affero General Public License along with
18 * this program; if not, see http://www.gnu.org/licenses or write to the Free
19 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
22 * You can contact SugarCRM, Inc. headquarters at 10050 North Wolfe Road,
23 * SW2-130, Cupertino, CA 95014, USA. or at email address contact@sugarcrm.com.
25 * The interactive user interfaces in modified source and object code versions
26 * of this program must display Appropriate Legal Notices, as required under
27 * Section 5 of the GNU Affero General Public License version 3.
29 * In accordance with Section 7(b) of the GNU Affero General Public License version 3,
30 * these Appropriate Legal Notices must retain the display of the "Powered by
31 * SugarCRM" logo. If the display of the logo is not reasonably feasible for
32 * technical reasons, the Appropriate Legal Notices must display the words
33 * "Powered by SugarCRM".
34 ********************************************************************************/
37 YAHOO.namespace("SUGAR");
40 Event = YAHOO.util.Event,
41 Connect = YAHOO.util.Connect,
45 * Message Box is a singleton widget designed to replace the browsers 'alert'
46 * function, as well as provide capabilities for pop-over loading bars and
47 * other small non-interactive pop-overs.
48 * TODO:Still needs configurable buttons in the footer as well as
49 * auto building of a loading bar.
52 progressTemplate : "{body}<br><div class='sugar-progress-wrap'><div class='sugar-progress-bar'/></div>",
53 promptTemplate : "{body}:<input id='sugar-message-prompt' class='sugar-message-prompt' name='sugar-message-prompt'></input>",
54 show: function(config) {
55 var myConf = sw.MessageBox.config = {
66 if (config['type'] && config['type'] == "prompt") {
67 myConf['buttons'] = [{
68 text: SUGAR.language.get("app_strings", "LBL_EMAIL_CANCEL"), handler:YAHOO.SUGAR.MessageBox.hide
70 text: SUGAR.language.get("app_strings", "LBL_EMAIL_OK"), handler:config['fn'] ?
72 var returnValue = config['fn'](YAHOO.util.Dom.get("sugar-message-prompt").value);
73 if (typeof(returnValue) == "undefined" || returnValue) {
74 YAHOO.SUGAR.MessageBox.hide();
77 : YAHOO.SUGAR.MessageBox.hide, isDefault:true
79 } else if ((config['type'] && config['type'] == "alert")) {
80 myConf['buttons'] = [{
81 text: SUGAR.language.get("app_strings", "LBL_EMAIL_OK"),
82 handler: config['fn'] ?
83 function(){YAHOO.SUGAR.MessageBox.hide(); config['fn']();}
85 YAHOO.SUGAR.MessageBox.hide,
88 } else if((config['type'] && config['type'] == "confirm")) {
89 myConf['buttons'] = [{
90 text: SUGAR.language.get("app_strings", "LBL_EMAIL_YES"), handler:config['fn'] ?
91 function(){config['fn']('yes');YAHOO.SUGAR.MessageBox.hide();}
92 : YAHOO.SUGAR.MessageBox.hide, isDefault:true
94 text: SUGAR.language.get("app_strings", "LBL_EMAIL_NO"), handler:config['fn'] ?
95 function(){config['fn']('no');YAHOO.SUGAR.MessageBox.hide();}
96 : YAHOO.SUGAR.MessageBox.hide
99 else if((config['type'] && config['type'] == "plain")) {
100 myConf['buttons'] = [];
103 for (var i in config) {
104 myConf[i] = config[i];
106 if (sw.MessageBox.panel) {
107 sw.MessageBox.panel.destroy();
109 sw.MessageBox.panel = new YAHOO.widget.SimpleDialog(myConf.id, {
110 width: myConf.width + 'px',
115 constraintoviewport: true,
117 buttons: myConf.buttons
119 if (myConf.type == "progress") {
120 sw.MessageBox.panel.setBody(sw.MessageBox.progressTemplate.replace(/\{body\}/gi, myConf.msg));
121 } else if (myConf.type == "prompt") {
122 sw.MessageBox.panel.setBody(sw.MessageBox.promptTemplate.replace(/\{body\}/gi, myConf.msg));
123 } else if (myConf.type == "confirm") {
124 sw.MessageBox.panel.setBody(myConf.msg);
126 sw.MessageBox.panel.setBody(myConf.msg);
128 sw.MessageBox.panel.setHeader(myConf.title);
130 if (myConf.beforeShow) {
131 sw.MessageBox.panel.beforeShowEvent.subscribe(function() {myConf.beforeShow();});
133 if (myConf.beforeHide) {
134 sw.MessageBox.panel.beforeHideEvent.subscribe(function() {myConf.beforeHide();});
136 sw.MessageBox.panel.render(document.body);
137 sw.MessageBox.panel.show();
140 updateProgress: function(percent, message) {
141 if (!sw.MessageBox.config.type == "progress") return;
143 if (typeof message == "string") {
144 sw.MessageBox.panel.setBody(sw.MessageBox.progressTemplate.replace(/\{body\}/gi, message));
147 var barEl = Dom.getElementsByClassName("sugar-progress-bar", null, YAHOO.SUGAR.MessageBox.panel.element)[0];
150 else if (percent < 0)
153 barEl.style.width = percent + "%";
157 if (sw.MessageBox.panel)
158 sw.MessageBox.panel.hide();
162 sw.Template = function(content) {
163 this._setContent(content);
166 sw.Template.prototype = {
167 regex : /\{([\w\.]*)\}/gim,
169 append: function (target, args) {
170 var tEl = Dom.get(target);
171 if (tEl) tEl.innerHTML += this.exec(args);
172 else if (typeof(console) != "undefined" && typeof(console.log) == "function")
173 console.log("Warning, unable to find target:" + target);
175 exec : function (args) {
176 var out = this.content;
177 for (var i in this.vars) {
178 var val = this._getValue(i, args);
179 var reg = new RegExp("\\{" + i + "\\}", "g");
180 out = out.replace(reg, val);
185 _setContent : function(content) {
186 this.content = content;
188 var result = this.regex.exec(content);
190 while(result && result.index > lastIndex){
191 lastIndex = result.index;
192 this.vars[result[1]] = true;
193 result = this.regex.exec(content);
197 _getValue: function(v, scope) {
198 return function(e) {return eval("this." + e);}.call(scope, v);
204 * SelectionGrid is simply a YUI Data Table with row selection already enabled.
206 sw.SelectionGrid = function(containerEl, columns, dataSource, config){
207 sw.SelectionGrid.superclass.constructor.call(this, containerEl, columns, dataSource, config);
208 // Subscribe to events for row selection
209 this.subscribe("rowMouseoverEvent", this.onEventHighlightRow);
210 this.subscribe("rowMouseoutEvent", this.onEventUnhighlightRow);
211 this.subscribe("rowClickEvent", this.onEventSelectRow);
212 // Programmatically select the first row
213 this.selectRow(this.getTrEl(0));
214 // Programmatically bring focus to the instance so arrow selection works immediately
218 YAHOO.extend(sw.SelectionGrid, YAHOO.widget.ScrollingDataTable, {
219 //Bugfix, the default getColumn will fail if a th element is passed in. http://yuilibrary.com/projects/yui2/ticket/2528034
220 getColumn : function(column) {
221 var oColumn = this._oColumnSet.getColumn(column);
224 // Validate TD element
225 var elCell = this.getTdEl(column);
226 if(elCell && (!column.tagName || column.tagName.toUpperCase() != "TH")) {
227 oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
229 // Validate TH element
231 elCell = this.getThEl(column);
235 var allColumns = this._oColumnSet.flat;
236 for(var i=0, len=allColumns.length; i<len; i++) {
237 if(allColumns[i].getThEl().id === elCell.id) {
238 oColumn = allColumns[i];
245 YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
253 * DragDropTable is a YUI Data Table with support for drag/drop row re-ordering.
255 sw.DragDropTable = function(containerEl, columns, dataSource, config){
256 var DDT = sw.DragDropTable;
257 DDT.superclass.constructor.call(this, containerEl, columns, dataSource, config);
258 this.DDGroup = config.group ? config.group : "defGroup";
259 //Add table to the dragdrop table groups
260 if (typeof DDT.groups[this.DDGroup] == "undefined")
261 DDT.groups[this.DDGroup] = [];
263 DDT.groups[this.DDGroup][DDT.groups[this.DDGroup].length] = this;
264 this.tabledd = new YAHOO.util.DDTarget(containerEl);
266 sw.DragDropTable.groups = {
270 YAHOO.extend(sw.DragDropTable, YAHOO.widget.ScrollingDataTable, {
271 _addTrEl : function (oRecord) {
272 var elTr = sw.DragDropTable.superclass._addTrEl.call(this, oRecord);
273 if (!this.disableEmptyRows || (
274 oRecord.getData()[this.getColumnSet().keys[0].key] != false
275 && oRecord.getData()[this.getColumnSet().keys[0].key] != "")
277 var _rowDD = new sw.RowDD(this, oRecord, elTr);
281 getGroup : function () {
282 return sw.DragDropTable.groups[this.DDGroup];
287 * subclass of DragDrop to allow rows to be picked up and dropped between other rows.
289 sw.RowDD = function(oDataTable, oRecord, elTr) {
290 if(oDataTable && oRecord && elTr) {
291 //sw.RowDD.superclass.constructor.call(this, elTr);
292 this.ddtable = oDataTable;
293 this.table = oDataTable.getTableEl();
296 this.newIndex = null;
298 this.initFrame(); // Needed for DDProxy
299 this.invalidHandleTypes = {};
304 YAHOO.extend(sw.RowDD, YAHOO.util.DDProxy, {
305 // _removeIdRegex : /(<.[^\/<]*)id\s*=\s*['|"]?\w*['|"]?([^>]*>)/gim,
306 _removeIdRegex : new RegExp("(<.[^\\/<]*)id\\s*=\\s*['|\"]?\w*['|\"]?([^>]*>)", "gim"),
308 _resizeProxy: function() {
309 this.constructor.superclass._resizeProxy.apply(this, arguments);
310 var dragEl = this.getDragEl(),
313 Dom.setStyle(this.pointer, 'height', (this.rowEl.offsetHeight + 5) + 'px');
314 Dom.setStyle(this.pointer, 'display', 'block');
315 var xy = Dom.getXY(el);
316 Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
318 Dom.setStyle(dragEl, 'height', this.rowEl.offsetHeight + "px");
319 Dom.setStyle(dragEl, 'width', (parseInt(Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
320 Dom.setXY(this.dragEl, xy);
323 startDrag: function(x, y) {
324 var dragEl = this.getDragEl();
325 var clickEl = this.getEl();
326 Dom.setStyle(clickEl, "opacity", "0.25");
327 var tableWrap = false;
328 if (clickEl.tagName.toUpperCase() == "TR")
330 dragEl.innerHTML = "<table>" + clickEl.innerHTML.replace(this._removeIdRegex, "$1$2") + "</table>";
331 //Dom.setStyle(dragEl, "color", Dom.getStyle(clickEl, "color"));
332 Dom.addClass(dragEl, "yui-dt-liner");
333 Dom.setStyle(dragEl, "height", (clickEl.clientHeight - 2) + "px");
334 Dom.setStyle(dragEl, "backgroundColor", Dom.getStyle(clickEl, "backgroundColor"));
335 Dom.setStyle(dragEl, "border", "2px solid gray");
336 Dom.setStyle(dragEl, "display", "");
338 this.newTable = this.ddtable;
341 clickValidator: function(e) {
342 if (this.row.getData()[0] == " ")
344 var target = Event.getTarget(e);
345 return ( this.isValidHandleChild(target) &&
346 (this.id == this.handleElId || this.DDM.handleWasClicked(target, this.id)) );
349 * This funciton checks that the target of the drag is a table row in this
350 * DDGroup and simply moves the sourceEL to that location as a preview.
352 onDragOver: function(ev, id) {
353 var groupTables = this.ddtable.getGroup();
354 for(i in groupTables) {
355 var targetTable = groupTables[i];
356 if (!targetTable.getContainerEl)
359 if (targetTable.getContainerEl().id == id) {
360 if (targetTable != this.newTable) { // Moved from one table to another
361 this.newIndex = targetTable.getRecordSet().getLength() - 1;
362 var destEl = Dom.get(targetTable.getLastTrEl());
363 destEl.parentNode.insertBefore(this.getEl(), destEl);
366 this.newTable = targetTable
371 if (this.newTable && this.newTable.getRecord(id)) { // Found the target row
372 var targetRow = this.newTable.getRecord(id);
373 var destEl = Dom.get(id);
374 destEl.parentNode.insertBefore(this.getEl(), destEl);
375 this.newIndex = this.newTable.getRecordIndex(targetRow);
379 endDrag: function() {
380 //Ensure the element is back on the home table to be cleaned up.
381 if (this.newTable != null && this.newIndex != null) {
382 this.getEl().style.display = "none";
383 this.table.appendChild(this.getEl());
384 this.newTable.addRow(this.row.getData(), this.newIndex);
386 //This method works, but throws an error under IE8
387 this.ddtable.deleteRow(this.row);
389 if(typeof(console) != "undefined" && console.log)
394 this.ddtable.render();
396 this.newTable = this.newIndex = null
398 var clickEl = this.getEl();
399 Dom.setStyle(clickEl, "opacity", "");
403 * A YUI panel that supports loading and re-loading it's contents from an Asynch request.
406 sw.AsyncPanel = function (el, params) {
408 sw.AsyncPanel.superclass.constructor.call(this, el, params);
410 sw.AsyncPanel.superclass.constructor.call(this, el);
413 YAHOO.extend(sw.AsyncPanel, YAHOO.widget.Panel, {
414 loadingText : "Loading...",
415 failureText : "Error loading content.",
417 load : function(url, method, callback) {
418 method = method ? method : "GET";
419 this.setBody(this.loadingText);
420 if (Connect.url) url = Connect.url + "&" + url;
421 this.callback = callback;
422 Connect.asyncRequest(method, url, {success:this._updateContent, failure:this._loadFailed, scope:this});
425 _updateContent : function (o) {
426 //Under safari, the width of the panel may expand for no apparent reason, and under FF it will contract
427 var w = this.cfg.config.width.value + "px";
428 this.setBody(o.responseText);
430 this.body.style.width = w
431 if (this.callback != null)
435 _loadFailed : function(o) {
436 this.setBody(this.failureText);
440 sw.ClosableTab = function(el, parent, conf) {
441 this.closeEvent = new YAHOO.util.CustomEvent("close", this);
443 sw.ClosableTab.superclass.constructor.call(this, el, conf);
445 sw.ClosableTab.superclass.constructor.call(this, el);
447 this.setAttributeConfig("TabView", {
450 this.get("labelEl").parentNode.href = "javascript:void(0);";
453 YAHOO.extend(sw.ClosableTab, YAHOO.widget.Tab, {
454 close : function () {
455 this.closeEvent.fire();
456 var parent = this.get("TabView");
457 parent.removeTab(this);
460 initAttributes: function(attr) {
461 sw.ClosableTab.superclass.initAttributes.call(this, attr);
464 * The message to display when closing the tab
465 * @attribute closeMsg
468 this.setAttributeConfig("closeMsg", {
469 value: attr.closeMsg || ""
473 * The tab's label text (or innerHTML).
477 this.setAttributeConfig("label", {
478 value: attr.label || this._getLabel(),
479 method: function(value) {
480 var labelEl = this.get("labelEl");
481 if (!labelEl) { // create if needed
482 this.set(LABEL_EL, this._createLabelEl());
485 labelEl.innerHTML = value;
487 var closeButton = document.createElement('a');
488 closeButton.href = "javascript:void(0);";
489 Dom.addClass(closeButton, "sugar-tab-close");
490 Event.addListener(closeButton, "click", function(e, tab){
491 if (tab.get("closeMsg") != "")
493 if (confirm(tab.get("closeMsg"))) {
503 labelEl.appendChild(closeButton);
512 * The sugar Tree is a YUI tree with node construction based on AJAX data built in.
514 sw.Tree = function (parentEl, baseRequestParams, rootParams) {
515 this.baseRequestParams = baseRequestParams;
516 sw.Tree.superclass.constructor.call(this, parentEl);
518 if (typeof rootParams == "string")
519 this.sendTreeNodeDataRequest(this.getRoot(), rootParams);
521 this.sendTreeNodeDataRequest(this.getRoot(), "");
525 YAHOO.extend(sw.Tree, YAHOO.widget.TreeView, {
526 sendTreeNodeDataRequest: function(parentNode, params){
527 YAHOO.util.Connect.asyncRequest('POST', 'index.php', {
528 success: this.handleTreeNodeDataRequest,
530 parentNode: parentNode
533 }, this.baseRequestParams + params);
535 handleTreeNodeDataRequest : function(o) {
536 var parentNode = o.argument.parentNode;
537 //parent.tree.removeChildren(parentNode);
538 var resp = YAHOO.lang.JSON.parse(o.responseText);
539 if (resp.tree_data.nodes) {
540 for (var i = 0; i < resp.tree_data.nodes.length; i++) {
541 var newChild = this.buildTreeNodeRecursive(resp.tree_data.nodes[i], parentNode);
544 parentNode.tree.draw();
547 buildTreeNodeRecursive : function(nodeData, parentNode) {
548 nodeData.label = nodeData.text;
549 var node = new YAHOO.widget.TextNode(nodeData, parentNode, nodeData.expanded);
550 if (typeof(nodeData.children) == 'object') {
551 for (var i = 0; i < nodeData.children.length; i++) {
552 this.buildTreeNodeRecursive(nodeData.children[i], node);
561 * A 1/2 second fade-in animation.
564 * @param el {HTMLElement} the element to animate
565 * @param callback {function} function to invoke when the animation is finished
567 YAHOO.widget.TVSlideIn = function(el, callback) {
569 * The element to animate
576 * the callback to invoke when the animation is complete
580 this.callback = callback;
582 this.logger = new YAHOO.widget.LogWriter(this.toString());
585 YAHOO.widget.TVSlideIn.prototype = {
587 * Performs the animation
590 animate: function() {
593 var s = this.el.style;
596 s.overflow = "hidden";
598 var th = this.el.clientHeight;
602 var a = new YAHOO.util.Anim(this.el, {height: {from: 0, to: th, unit:"px"}}, dur);
603 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
608 * Clean up and invoke callback
611 onComplete: function() {
612 this.el.style.overflow = "";
613 this.el.style.height = "";
620 * @return {string} the string representation of the instance
622 toString: function() {
628 * A 1/2 second fade out animation.
631 * @param el {HTMLElement} the element to animate
632 * @param callback {Function} function to invoke when the animation is finished
634 YAHOO.widget.TVSlideOut = function(el, callback) {
636 * The element to animate
643 * the callback to invoke when the animation is complete
647 this.callback = callback;
649 this.logger = new YAHOO.widget.LogWriter(this.toString());
652 YAHOO.widget.TVSlideOut.prototype = {
654 * Performs the animation
657 animate: function() {
660 var th = this.el.clientHeight;
661 this.el.style.overflow = "hidden";
662 var a = new YAHOO.util.Anim(this.el, {height: {from: th, to: 0, unit:"px"}}, dur);
663 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
668 * Clean up and invoke callback
671 onComplete: function() {
672 var s = this.el.style;
674 this.el.style.overflow = "";
675 this.el.style.height = "";
682 * @return {string} the string representation of the instance
684 toString: function() {
693 * Problem: SODA tests rely on grabbing report field names by IDs. However,
694 * these ID's don't always seem to be consistent as they are auto-generated by YUI.
695 * Here I have replaced the constructor to set the id of the row element to the modulename
696 * concatenated with the fieldname if the report flag has been set, otherwise proceed as normal.
701 var temp = YAHOO.widget.Record.prototype;
702 YAHOO.widget.Record = function(oLiteral) {
703 this._nCount = YAHOO.widget.Record._nCount;
705 YAHOO.widget.Record._nCount++;
707 if (YAHOO.lang.isObject(oLiteral)) {
708 for (var sKey in oLiteral) {
709 if (YAHOO.lang.hasOwnProperty(oLiteral, sKey)) {
710 this._oData[sKey] = oLiteral[sKey];
715 if (SUGAR.reports && SUGAR.reports.overrideRecord) {
716 this._sId = this._oData.module_name + "_" + this._oData.field_name;
718 this._sId = Dom.generateId(null, "yui-rec"); //"yui-rec" + this._nCount;
721 YAHOO.widget.Record._nCount = 0;
722 YAHOO.widget.Record.prototype = temp;