1 /*********************************************************************************
2 * SugarCRM Community Edition is a customer relationship management program developed by
3 * SugarCRM, Inc. Copyright (C) 2004-2011 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 (!SUGAR.isIE) console.log("Warning, unable to find target:" + target);
174 exec : function (args) {
175 var out = this.content;
176 for (var i in this.vars) {
177 var val = this._getValue(i, args);
178 var reg = new RegExp("\\{" + i + "\\}", "g");
179 out = out.replace(reg, val);
184 _setContent : function(content) {
185 this.content = content;
187 var result = this.regex.exec(content);
189 while(result && result.index > lastIndex){
190 lastIndex = result.index;
191 this.vars[result[1]] = true;
192 result = this.regex.exec(content);
196 _getValue: function(v, scope) {
197 return function(e) {return eval("this." + e);}.call(scope, v);
203 * SelectionGrid is simply a YUI Data Table with row selection already enabled.
205 sw.SelectionGrid = function(containerEl, columns, dataSource, config){
206 sw.SelectionGrid.superclass.constructor.call(this, containerEl, columns, dataSource, config);
207 // Subscribe to events for row selection
208 this.subscribe("rowMouseoverEvent", this.onEventHighlightRow);
209 this.subscribe("rowMouseoutEvent", this.onEventUnhighlightRow);
210 this.subscribe("rowClickEvent", this.onEventSelectRow);
211 // Programmatically select the first row
212 this.selectRow(this.getTrEl(0));
213 // Programmatically bring focus to the instance so arrow selection works immediately
217 YAHOO.extend(sw.SelectionGrid, YAHOO.widget.ScrollingDataTable, {
218 //Bugfix, the default getColumn will fail if a th element is passed in. http://yuilibrary.com/projects/yui2/ticket/2528034
219 getColumn : function(column) {
220 var oColumn = this._oColumnSet.getColumn(column);
223 // Validate TD element
224 var elCell = this.getTdEl(column);
225 if(elCell && (!column.tagName || column.tagName.toUpperCase() != "TH")) {
226 oColumn = this._oColumnSet.getColumn(elCell.cellIndex);
228 // Validate TH element
230 elCell = this.getThEl(column);
234 var allColumns = this._oColumnSet.flat;
235 for(var i=0, len=allColumns.length; i<len; i++) {
236 if(allColumns[i].getThEl().id === elCell.id) {
237 oColumn = allColumns[i];
244 YAHOO.log("Could not get Column for column at " + column, "info", this.toString());
252 * DragDropTable is a YUI Data Table with support for drag/drop row re-ordering.
254 sw.DragDropTable = function(containerEl, columns, dataSource, config){
255 var DDT = sw.DragDropTable;
256 DDT.superclass.constructor.call(this, containerEl, columns, dataSource, config);
257 this.DDGroup = config.group ? config.group : "defGroup";
258 //Add table to the dragdrop table groups
259 if (typeof DDT.groups[this.DDGroup] == "undefined")
260 DDT.groups[this.DDGroup] = [];
262 DDT.groups[this.DDGroup][DDT.groups[this.DDGroup].length] = this;
263 this.tabledd = new YAHOO.util.DDTarget(containerEl);
265 sw.DragDropTable.groups = {
269 YAHOO.extend(sw.DragDropTable, YAHOO.widget.ScrollingDataTable, {
270 _addTrEl : function (oRecord) {
271 var elTr = sw.DragDropTable.superclass._addTrEl.call(this, oRecord);
272 if (!this.disableEmptyRows || (
273 oRecord.getData()[this.getColumnSet().keys[0].key] != false
274 && oRecord.getData()[this.getColumnSet().keys[0].key] != "")
276 var _rowDD = new sw.RowDD(this, oRecord, elTr);
280 getGroup : function () {
281 return sw.DragDropTable.groups[this.DDGroup];
286 * subclass of DragDrop to allow rows to be picked up and dropped between other rows.
288 sw.RowDD = function(oDataTable, oRecord, elTr) {
289 if(oDataTable && oRecord && elTr) {
290 //sw.RowDD.superclass.constructor.call(this, elTr);
291 this.ddtable = oDataTable;
292 this.table = oDataTable.getTableEl();
295 this.newIndex = null;
297 this.initFrame(); // Needed for DDProxy
298 this.invalidHandleTypes = {};
303 YAHOO.extend(sw.RowDD, YAHOO.util.DDProxy, {
304 // _removeIdRegex : /(<.[^\/<]*)id\s*=\s*['|"]?\w*['|"]?([^>]*>)/gim,
305 _removeIdRegex : new RegExp("(<.[^\\/<]*)id\\s*=\\s*['|\"]?\w*['|\"]?([^>]*>)", "gim"),
307 _resizeProxy: function() {
308 this.constructor.superclass._resizeProxy.apply(this, arguments);
309 var dragEl = this.getDragEl(),
312 Dom.setStyle(this.pointer, 'height', (this.rowEl.offsetHeight + 5) + 'px');
313 Dom.setStyle(this.pointer, 'display', 'block');
314 var xy = Dom.getXY(el);
315 Dom.setXY(this.pointer, [xy[0], (xy[1] - 5)]);
317 Dom.setStyle(dragEl, 'height', this.rowEl.offsetHeight + "px");
318 Dom.setStyle(dragEl, 'width', (parseInt(Dom.getStyle(dragEl, 'width'),10) + 4) + 'px');
319 Dom.setXY(this.dragEl, xy);
322 startDrag: function(x, y) {
323 var dragEl = this.getDragEl();
324 var clickEl = this.getEl();
325 Dom.setStyle(clickEl, "opacity", "0.25");
326 var tableWrap = false;
327 if (clickEl.tagName.toUpperCase() == "TR")
329 dragEl.innerHTML = "<table>" + clickEl.innerHTML.replace(this._removeIdRegex, "$1$2") + "</table>";
330 //Dom.setStyle(dragEl, "color", Dom.getStyle(clickEl, "color"));
331 Dom.addClass(dragEl, "yui-dt-liner");
332 Dom.setStyle(dragEl, "height", (clickEl.clientHeight - 2) + "px");
333 Dom.setStyle(dragEl, "backgroundColor", Dom.getStyle(clickEl, "backgroundColor"));
334 Dom.setStyle(dragEl, "border", "2px solid gray");
335 Dom.setStyle(dragEl, "display", "");
337 this.newTable = this.ddtable;
340 clickValidator: function(e) {
341 if (this.row.getData()[0] == " ")
343 var target = Event.getTarget(e);
344 return ( this.isValidHandleChild(target) &&
345 (this.id == this.handleElId || this.DDM.handleWasClicked(target, this.id)) );
348 * This funciton checks that the target of the drag is a table row in this
349 * DDGroup and simply moves the sourceEL to that location as a preview.
351 onDragOver: function(ev, id) {
352 var groupTables = this.ddtable.getGroup();
353 for(i in groupTables) {
354 var targetTable = groupTables[i];
355 if (!targetTable.getContainerEl)
358 if (targetTable.getContainerEl().id == id) {
359 if (targetTable != this.newTable) { // Moved from one table to another
360 this.newIndex = targetTable.getRecordSet().getLength() - 1;
361 var destEl = Dom.get(targetTable.getLastTrEl());
362 destEl.parentNode.insertBefore(this.getEl(), destEl);
365 this.newTable = targetTable
370 if (this.newTable && this.newTable.getRecord(id)) { // Found the target row
371 var targetRow = this.newTable.getRecord(id);
372 var destEl = Dom.get(id);
373 destEl.parentNode.insertBefore(this.getEl(), destEl);
374 this.newIndex = this.newTable.getRecordIndex(targetRow);
378 endDrag: function() {
379 //Ensure the element is back on the home table to be cleaned up.
380 if (this.newTable != null && this.newIndex != null) {
381 this.getEl().style.display = "none";
382 this.table.appendChild(this.getEl());
383 this.newTable.addRow(this.row.getData(), this.newIndex);
384 this.ddtable.deleteRow(this.row);
385 this.ddtable.render();
387 this.newTable = this.newIndex = null
389 var clickEl = this.getEl();
390 Dom.setStyle(clickEl, "opacity", "");
394 * A YUI panel that supports loading and re-loading it's contents from an Asynch request.
397 sw.AsyncPanel = function (el, params) {
399 sw.AsyncPanel.superclass.constructor.call(this, el, params);
401 sw.AsyncPanel.superclass.constructor.call(this, el);
404 YAHOO.extend(sw.AsyncPanel, YAHOO.widget.Panel, {
405 loadingText : "Loading...",
406 failureText : "Error loading content.",
408 load : function(url, method, callback) {
409 method = method ? method : "GET";
410 this.setBody(this.loadingText);
411 if (Connect.url) url = Connect.url + "&" + url;
412 this.callback = callback;
413 Connect.asyncRequest(method, url, {success:this._updateContent, failure:this._loadFailed, scope:this});
416 _updateContent : function (o) {
417 //Under safari, the width of the panel may expand for no apparent reason, and under FF it will contract
418 var w = this.cfg.config.width.value + "px";
419 this.setBody(o.responseText);
421 this.body.style.width = w
422 if (this.callback != null)
426 _loadFailed : function(o) {
427 this.setBody(this.failureText);
431 sw.ClosableTab = function(el, parent, conf) {
432 this.closeEvent = new YAHOO.util.CustomEvent("close", this);
434 sw.ClosableTab.superclass.constructor.call(this, el, conf);
436 sw.ClosableTab.superclass.constructor.call(this, el);
438 this.setAttributeConfig("TabView", {
441 this.get("labelEl").parentNode.href = "javascript:void(0);";
444 YAHOO.extend(sw.ClosableTab, YAHOO.widget.Tab, {
445 close : function () {
446 this.closeEvent.fire();
447 var parent = this.get("TabView");
448 parent.removeTab(this);
451 initAttributes: function(attr) {
452 sw.ClosableTab.superclass.initAttributes.call(this, attr);
455 * The message to display when closing the tab
456 * @attribute closeMsg
459 this.setAttributeConfig("closeMsg", {
460 value: attr.closeMsg || ""
464 * The tab's label text (or innerHTML).
468 this.setAttributeConfig("label", {
469 value: attr.label || this._getLabel(),
470 method: function(value) {
471 var labelEl = this.get("labelEl");
472 if (!labelEl) { // create if needed
473 this.set(LABEL_EL, this._createLabelEl());
476 labelEl.innerHTML = value;
478 var closeButton = document.createElement('a');
479 closeButton.href = "javascript:void(0);";
480 Dom.addClass(closeButton, "sugar-tab-close");
481 Event.addListener(closeButton, "click", function(e, tab){
482 if (tab.get("closeMsg") != "")
484 if (confirm(tab.get("closeMsg"))) {
494 labelEl.appendChild(closeButton);
503 * The sugar Tree is a YUI tree with node construction based on AJAX data built in.
505 sw.Tree = function (parentEl, baseRequestParams, rootParams) {
506 this.baseRequestParams = baseRequestParams;
507 sw.Tree.superclass.constructor.call(this, parentEl);
509 if (typeof rootParams == "string")
510 this.sendTreeNodeDataRequest(this.getRoot(), rootParams);
512 this.sendTreeNodeDataRequest(this.getRoot(), "");
516 YAHOO.extend(sw.Tree, YAHOO.widget.TreeView, {
517 sendTreeNodeDataRequest: function(parentNode, params){
518 YAHOO.util.Connect.asyncRequest('POST', 'index.php', {
519 success: this.handleTreeNodeDataRequest,
521 parentNode: parentNode
524 }, this.baseRequestParams + params);
526 handleTreeNodeDataRequest : function(o) {
527 var parentNode = o.argument.parentNode;
528 //parent.tree.removeChildren(parentNode);
529 var resp = YAHOO.lang.JSON.parse(o.responseText);
530 if (resp.tree_data.nodes) {
531 for (var i = 0; i < resp.tree_data.nodes.length; i++) {
532 var newChild = this.buildTreeNodeRecursive(resp.tree_data.nodes[i], parentNode);
535 parentNode.tree.draw();
538 buildTreeNodeRecursive : function(nodeData, parentNode) {
539 nodeData.label = nodeData.text;
540 var node = new YAHOO.widget.TextNode(nodeData, parentNode, nodeData.expanded);
541 if (typeof(nodeData.children) == 'object') {
542 for (var i = 0; i < nodeData.children.length; i++) {
543 this.buildTreeNodeRecursive(nodeData.children[i], node);
552 * A 1/2 second fade-in animation.
555 * @param el {HTMLElement} the element to animate
556 * @param callback {function} function to invoke when the animation is finished
558 YAHOO.widget.TVSlideIn = function(el, callback) {
560 * The element to animate
567 * the callback to invoke when the animation is complete
571 this.callback = callback;
573 this.logger = new YAHOO.widget.LogWriter(this.toString());
576 YAHOO.widget.TVSlideIn.prototype = {
578 * Performs the animation
581 animate: function() {
584 var s = this.el.style;
587 s.overflow = "hidden";
589 var th = this.el.clientHeight;
593 var a = new YAHOO.util.Anim(this.el, {height: {from: 0, to: th, unit:"px"}}, dur);
594 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
599 * Clean up and invoke callback
602 onComplete: function() {
603 this.el.style.overflow = "";
604 this.el.style.height = "";
611 * @return {string} the string representation of the instance
613 toString: function() {
619 * A 1/2 second fade out animation.
622 * @param el {HTMLElement} the element to animate
623 * @param callback {Function} function to invoke when the animation is finished
625 YAHOO.widget.TVSlideOut = function(el, callback) {
627 * The element to animate
634 * the callback to invoke when the animation is complete
638 this.callback = callback;
640 this.logger = new YAHOO.widget.LogWriter(this.toString());
643 YAHOO.widget.TVSlideOut.prototype = {
645 * Performs the animation
648 animate: function() {
651 var th = this.el.clientHeight;
652 this.el.style.overflow = "hidden";
653 var a = new YAHOO.util.Anim(this.el, {height: {from: th, to: 0, unit:"px"}}, dur);
654 a.onComplete.subscribe( function() { tvanim.onComplete(); } );
659 * Clean up and invoke callback
662 onComplete: function() {
663 var s = this.el.style;
665 this.el.style.overflow = "";
666 this.el.style.height = "";
673 * @return {string} the string representation of the instance
675 toString: function() {
684 * Problem: SODA tests rely on grabbing report field names by IDs. However,
685 * these ID's don't always seem to be consistent as they are auto-generated by YUI.
686 * Here I have replaced the constructor to set the id of the row element to the modulename
687 * concatenated with the fieldname if the report flag has been set, otherwise proceed as normal.
692 var temp = YAHOO.widget.Record.prototype;
693 YAHOO.widget.Record = function(oLiteral) {
694 this._nCount = YAHOO.widget.Record._nCount;
696 YAHOO.widget.Record._nCount++;
698 if (YAHOO.lang.isObject(oLiteral)) {
699 for (var sKey in oLiteral) {
700 if (YAHOO.lang.hasOwnProperty(oLiteral, sKey)) {
701 this._oData[sKey] = oLiteral[sKey];
706 if (SUGAR.reports && SUGAR.reports.overrideRecord) {
707 this._sId = this._oData.module_name + "_" + this._oData.field_name;
709 this._sId = Dom.generateId(null, "yui-rec"); //"yui-rec" + this._nCount;
712 YAHOO.widget.Record._nCount = 0;
713 YAHOO.widget.Record.prototype = temp;