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 if(typeof(SUGAR.collection) == "undefined") {
38 SUGAR.collection = function(form_name, field_name, module, popupData){
41 * boolean variable to handle expand/collapse views
42 * false if the collection field is collapsed and true if the rows are expanded.
44 this.more_status = false;
47 * Store the form name containing this field. Example: EditView
49 this.form = form_name;
52 * Store the name of the collection field. Example: account_name
54 this.field = field_name;
58 * Store the unique form + field name that uses the combination of form and field
60 this.field_element_name = this.form + '_' + this.field;
63 * Store the name of the module from where come the field. Example: Accounts
68 * Number of secondaries linked records (total of linked records - 1).
70 this.fields_count = 0;
73 * Number of extra fields.
75 this.extra_fields_count = 0;
78 * Set to true if it is the initialization.
83 * Name of the primary field. Example: "accounts_collection_0"
85 this.primary_field = "";
88 * Store the row cloned in key "0" and the context cloned in key "1".
90 this.cloneField = new Array();
93 * Store the sqs_objects for the cloned row encoded in JSON.
98 * Store the name and the id of all the secondaries linked records. this is used to create the secondary rows.
100 this.secondaries_values = new Array();
103 * Store all the extra fields which has been updated in the collection field to save on save of the main record.
105 this.update_fields = new Object();
108 * boolean variable indicating whether or not to show the expand/collapse arrow
110 this.show_more_image = true;
114 SUGAR.collection.prototype = {
116 * Remove the row designated by the passed 'id' or clear the row if there is only one row.
118 remove: function(num){
119 // if there is only one record, clear it instead of removing it
120 // this is determined by the visibility of the drop down arrow element
121 var radio_els = this.get_radios();
123 if(radio_els.length == 1) {
124 div_el = document.getElementById(this.field_element_name + '_input_div_' + num);
125 var input_els = div_el.getElementsByTagName('input');
127 input_els[0].value = '';
130 input_els[1].value = '';
132 if(this.primary_field) {
133 div_el = document.getElementById(this.field_element_name + '_radio_div_' + num);
134 radio_els = div_el.getElementsByTagName('input');
135 //Clear the radio field
136 radio_els[0].checked = false;
139 div_el = document.getElementById(this.field_element_name + '_input_div_' + num);
141 div_el = document.getElementById(this.field_element_name + '_radio_div_' + num);
142 var tr_to_remove = document.getElementById('lineFields_' + this.field_element_name + '_' + num);
143 div_el.parentNode.parentNode.parentNode.removeChild(tr_to_remove);
145 var div_id = 'lineFields_' + this.field_element_name + '_' + num;
146 if (typeof sqs_objects[div_id.replace("_field_", "_")] != 'undefined') {
147 delete (sqs_objects[div_id.replace("_field_", "_")]);
150 for(var k=0; k< radio_els.length; k++){
151 if(radio_els[k].checked){
155 // If we remove an entry marked as the primary, set another record as the primary
156 var primary_checked = document.forms[this.form].elements[this.field+"_allowed_to_check"];
157 var allowed_to_check = true;
158 if(primary_checked && primary_checked.value == 'false'){
159 allowed_to_check = false;
161 if(/EditView/.test(this.form) && !checked && typeof radio_els[0] != 'undefined' && allowed_to_check) {
162 radio_els[0].checked = true;
163 this.changePrimary(true);
167 // if there is now only one record, hide the "more..." link
168 if(radio_els.length == 1){
169 this.more_status = false;
170 if (document.getElementById('more_'+this.field_element_name) && document.getElementById('more_'+this.field_element_name).style.display != 'none') {
171 document.getElementById('more_'+this.field_element_name).style.display='none';
174 this.show_arrow_label(false);
183 get_radios: function() {
184 return YAHOO.util.Selector.query('input[name^=primary]', document.getElementById(this.field_element_name+'_table'));
188 * Add a new empty row.
190 add: function(values){
192 var Field0 = this.init_clone(values);
193 this.cloneField[1].appendChild(Field0);
194 //Enable quicksearch for this field
196 this.changePrimary(false);
198 //If the arrow field and label are collapsed, un-collapse it
199 if(document.getElementById('more_'+this.field_element_name) && document.getElementById('more_'+this.field_element_name).style.display == 'none'){
200 document.getElementById('more_'+this.field_element_name).style.display='';
203 if(!this.is_expanded()) {
205 this.show_arrow_label(true);
210 * Add the secondaries rows on load of the page.
212 add_secondaries: function(){
213 var clone_id = this.form + '_' + this.field + '_collection_0';
214 YAHOO.util.Event.onContentReady(clone_id, function(c){
217 c.changePrimary(true);
218 for(key in c.secondaries_values){
219 if (isInteger(key)) {
220 c.add(c.secondaries_values[key]);
224 // Update the "hash" of the unchanged form, because this is just adding data, not actually changing anything
225 initEditView(document.forms[c.form]);
229 * Create the new row from a cloned row.
231 init_clone: function(values){
233 //Safety check, this means that the clone field was not created yet
234 if(typeof this.cloneField[0] == 'undefined') {
238 if (typeof values == "undefined") {
239 values = new Array();
244 var count = this.fields_count;
246 //Clone the table element containing the fields for each row, use safe_clone uder IE to prevent events from being cloned
247 var Field0 = SUGAR.isIE ?
248 SUGAR.collection.safe_clone(this.cloneField[0], true) :
249 this.cloneField[0].cloneNode(true);
251 Field0.id = "lineFields_"+this.field_element_name+"_"+count;
253 for ( var ii = 0; ii < Field0.childNodes.length; ii++ ){
254 if(typeof(Field0.childNodes[ii].tagName) != 'undefined' && Field0.childNodes[ii].tagName == "TD") {
255 for (var jj = 0; jj < Field0.childNodes[ii].childNodes.length; jj++) {
256 currentNode = Field0.childNodes[ii].childNodes[jj];
257 this.process_node(Field0.childNodes[ii], currentNode, values);
266 * method to process cloning of nodes, moved out of init_clone so that
267 * this may be recursively called
269 process_node: function(parentNode, currentNode, values) {
270 if(parentNode.className == 'td_extra_field'){
271 // If this is an extra field
275 var toreplace = this.field + "_collection_extra_0";
276 var re = new RegExp(toreplace, 'g');
277 parentNode.innerHTML = parentNode.innerHTML.replace(re, this.field + "_collection_extra_" + this.fields_count);
278 } else if (currentNode.tagName && currentNode.tagName == 'SPAN') {
279 //If it is our div element, recursively find all input elements to process
280 currentNode.id = /_input/.test(currentNode.id) ? this.field_element_name + '_input_div_' + this.fields_count : this.field_element_name + '_radio_div_' + this.fields_count;
281 if (/_input/.test(currentNode.id)) {
282 currentNode.name = 'teamset_div';
285 var input_els = currentNode.getElementsByTagName('input');
286 for ( var x = 0; x < input_els.length; x++ ){
288 //if the input tag id is blank (IE bug), then set it equal to that of the parent span id
289 if(typeof(input_els[x].id) == 'undefined' || input_els[x].id == '') {
290 input_els[x].id = currentNode.id;
293 if(input_els[x].tagName && input_els[x].tagName == 'INPUT') {
294 this.process_node(parentNode, input_els[x], values);
297 } else if (currentNode.name) {
298 // If this is a standard field
299 var toreplace = this.field + "_collection_0";
300 var re = new RegExp(toreplace, 'g');
301 var name = currentNode.name;
302 var new_name = name.replace(re, this.field + "_collection_" + this.fields_count);
303 var new_id = currentNode.id.replace(re, this.field + "_collection_" + this.fields_count);
307 var sqs_id = this.form + '_' + new_name;
308 if (typeof this.sqs_clone != 'undefined') {
309 var sqs_clone = YAHOO.lang.JSON.stringify(this.sqs_clone);
310 eval('sqs_objects[sqs_id]=' + sqs_clone);
312 for (var pop_field in sqs_objects[sqs_id]['populate_list']) {
313 if (typeof sqs_objects[sqs_id]['populate_list'][pop_field] == 'string') {
314 sqs_objects[sqs_id]['populate_list'][pop_field] = sqs_objects[sqs_id]['populate_list'][pop_field].replace(RegExp('_0', 'g'), "_" + this.fields_count);
317 for (var req_field in sqs_objects[sqs_id]['required_list']) {
318 if (typeof sqs_objects[sqs_id]['required_list'][req_field] == 'string') {
319 sqs_objects[sqs_id]['required_list'][req_field] = sqs_objects[sqs_id]['required_list'][req_field].replace(RegExp('_0', 'g'), "_" + this.fields_count);
324 currentNode.name = new_name;
325 currentNode.id = new_id;
326 currentNode.value = values['name'];
328 case "id_" + toreplace:
329 currentNode.name = new_name.replace(RegExp('_0', 'g'), "_" + this.fields_count);
330 currentNode.id = new_id.replace(RegExp('_0', 'g'), "_" + this.fields_count);
331 currentNode.value = values['id'];
333 case "btn_" + toreplace:
334 currentNode.name = new_name;
335 currentNode.attributes['onclick'].value = currentNode.attributes['onclick'].value.replace(re, this.field + "_collection_" + this.fields_count);
336 currentNode.attributes['onclick'].value = currentNode.attributes['onclick'].value.replace(RegExp(this.field + "_collection_extra_0", 'g'), this.field + "_collection_extra_" + this.fields_count);
338 case "allow_new_value_" + toreplace:
339 currentNode.name = new_name;
340 currentNode.id = new_id;
342 case "remove_" + toreplace:
343 currentNode.name = new_name;
344 currentNode.id = new_id;
345 currentNode.setAttribute('collection_id', this.field_element_name);
346 currentNode.setAttribute('remove_id', this.fields_count);
347 currentNode.onclick = function() {
348 collection[this.getAttribute('collection_id')].remove(this.getAttribute('remove_id'));
351 case "primary_" + this.field + "_collection":
352 currentNode.id = new_id;
353 currentNode.value = this.fields_count;
354 currentNode.checked = false; //Firefox
355 currentNode.setAttribute('defaultChecked', '');
358 alert(toreplace + '|' + currentNode.name + '|' + name + '|' + new_name);
366 * Collapse or expand the rows to show for the editview(depending of the this.more_status attribute).
368 js_more: function(val){
369 if(this.show_more_image){
370 var more_ = document.getElementById('more_img_'+this.field_element_name);
371 var arrow = document.getElementById('arrow_'+this.field);
372 var radios = this.get_radios();
373 // if we want to collapse
374 if(this.more_status == false){
375 more_.src = "index.php?entryPoint=getImage&themeName="+SUGAR.themes.theme_name+"&imageName=advanced_search.gif";
376 this.more_status = true;
377 // show the primary only and hidde the other one.
378 var hidden_count = 0;
379 for(var k=0; k< radios.length; k++){
380 if (radios[k].type && radios[k].type == 'radio') {
381 if (radios[k].checked) {
382 radios[k].parentNode.parentNode.parentNode.style.display = '';
384 radios[k].parentNode.parentNode.parentNode.style.display = 'none';
389 //rrs - add code to not remove the first field if non if the fields are selected as primary
390 if(hidden_count == radios.length){
391 radios[0].parentNode.parentNode.parentNode.style.display = '';
394 arrow.value = 'hide';
396 more_.src = "index.php?entryPoint=getImage&themeName="+SUGAR.themes.theme_name+"&imageName=basic_search.gif";
397 this.more_status=false;
398 // display all the records
399 for(var k=0; k< radios.length; k++){
401 radios[k].parentNode.parentNode.parentNode.style.display='';
405 arrow.value = 'show';
408 var more_div = document.getElementById('more_div_'+this.field_element_name);
410 more_div.innerHTML = arrow.value == 'show' ? SUGAR.language.get('app_strings','LBL_HIDE') : SUGAR.language.get('app_strings','LBL_SHOW');
416 * Create the clone on load of the page and store it in this.cloneField
418 create_clone: function() {
419 var oneField = document.getElementById('lineFields_'+this.field_element_name+'_0');
420 this.cloneField[0] = SUGAR.isIE ?
421 SUGAR.collection.safe_clone(oneField, true) :
422 oneField.cloneNode(true);
423 this.cloneField[1] = oneField.parentNode;
424 //fixing bug @48829: Team field shows fully expanded multiple teams instead of hiding multiple teams
425 //this.more_status = true;
426 var clone_id = this.form + '_' + this.field + '_collection_0';
428 if (typeof sqs_objects != 'undefined' && typeof sqs_objects[clone_id] != 'undefined') {
429 var clone = YAHOO.lang.JSON.stringify(sqs_objects[clone_id]);
430 eval('this.sqs_clone=' + clone);
434 * Validates team set to check if the primary team id has been set or not
436 validateTemSet : function(formname, fieldname) {
437 var table_element_id = formname + '_' + fieldname + '_table';
438 if(document.getElementById(table_element_id)) {
439 var input_elements = YAHOO.util.Selector.query('input[type=radio]', document.getElementById(table_element_id));
440 var has_primary = false;
441 var primary_field_id = fieldname + '_collection_0';
442 for(t in input_elements) {
443 primary_field_id = fieldname + '_collection_' + input_elements[t].value;
444 if(input_elements[t].type && input_elements[t].type == 'radio' && input_elements[t].checked == true) {
445 if(document.forms[formname].elements[primary_field_id].value != '') {
459 * return an array of teamids for a team field
461 getTeamIdsfromUI: function(formname, fieldname) {
462 var team_ids = new Array();
463 var table_element_id = formname + '_' + fieldname + '_table';
464 if(document.getElementById(table_element_id)) {
465 input_elements = YAHOO.util.Selector.query('input[type=hidden]', document.getElementById(table_element_id));
466 for(t = 0; t < input_elements.length; t++) {
467 if (input_elements[t].id.match( fieldname + "_collection_") != null) {
468 team_ids.push(input_elements[t].value);
475 * return a primary team id
477 getPrimaryTeamidsFromUI: function(formname, fieldname) {
478 var table_element_id = formname + '_' + fieldname + '_table';
479 if(document.getElementById(table_element_id)) {
480 var input_elements = YAHOO.util.Selector.query('input[type=radio]', document.getElementById(table_element_id));
481 for(t in input_elements) {
482 var primary_field_id = 'id_' + document.forms[formname][fieldname].name + '_collection_' + input_elements[t].value;
483 if(input_elements[t].type && input_elements[t].type == 'radio' && input_elements[t].checked == true) {
484 if(document.forms[formname].elements[primary_field_id].value != '') {
485 return document.forms[formname].elements[primary_field_id].value;
493 * Change the primary row onchange of the radio button.
495 changePrimary: function(noAdd){
496 var old_primary = this.primary_field;
497 var radios=this.get_radios();
498 for (var k = 0; k < radios.length; k++) {
499 var qs_id = radios[k].id.replace('primary_','');
500 if (radios[k].checked) {
501 this.primary_field = qs_id;
503 qs_id = qs_id + '_' + k;
506 qs_id = this.form + '_' + qs_id;
508 if(typeof sqs_objects != 'undefined' && typeof sqs_objects[qs_id] != 'undefined' && sqs_objects[qs_id]['primary_field_list']){
509 for (var ii = 0; ii < sqs_objects[qs_id]['primary_field_list'].length; ii++) {
510 if (radios[k].checked && qs_id != old_primary) {
511 sqs_objects[qs_id]['field_list'].push(sqs_objects[qs_id]['primary_field_list'][ii]);
512 sqs_objects[qs_id]['populate_list'].push(sqs_objects[qs_id]['primary_populate_list'][ii]);
513 }else if(old_primary == qs_id && !radios[k].checked){
514 sqs_objects[qs_id]['field_list'].pop();
515 sqs_objects[qs_id]['populate_list'].pop();
527 * Collapse or expand the rows to show for the detailview.
529 js_more_detail: function(id){
530 var more_img = document.getElementById('more_img_'+id);
531 if(more_img.style.display == 'inline'){
532 more_img.src = "index.php?entryPoint=getImage&themeName="+SUGAR.themes.theme_name+"&imageName=advanced_search.gif";
534 more_img.src = "index.php?entryPoint=getImage&themeName="+SUGAR.themes.theme_name+"&imageName=basic_search.gif";
538 * Replace the first field with the specified values
540 replace_first: function(values){
541 for (var i = 0; i <= this.fields_count; i++) {
542 var div_el = document.getElementById(this.field_element_name + '_input_div_' + i);
544 var name_field = document.getElementById(this.field_element_name+"_collection_" + i);
545 var id_field = document.getElementById("id_"+this.field_element_name+"_collection_" + i);
546 name_field.value = values['name'];
547 id_field.value = values['id'];
553 * Remove all empty fields from the widget.
555 clean_up: function(){
556 //clean up any rows that have been added but do not contain any data
557 var divsToClean = new Array();
558 var isFirstFieldEmpty = false;
560 for (var i = 0; i <= this.fields_count; i++) {
561 var div_el = document.getElementById(this.field_element_name + '_input_div_' + i);
563 input_els = div_el.getElementsByTagName('input');
564 for ( var x = 0; x < input_els.length; x++ ){
565 if(input_els[x].id && input_els[x].name == (this.field + '_collection_' + i) && trim(input_els[x].value) == '') {
567 isFirstFieldEmpty = true;
578 for(var j = 0; j < divsToClean.length; j++){
579 this.remove(divsToClean[j]);
581 return isFirstFieldEmpty;
584 show_arrow_label: function(show) {
585 var more_div = document.getElementById('more_div_'+this.field_element_name);
587 more_div.style.display = show ? '' : 'none';
593 * helper function to determine whether or not the widget is expanded (all teams are shown)
595 is_expanded: function() {
596 var more_div = document.getElementById('more_div_'+this.field_element_name);
598 return more_div.style.display == '';
604 SUGAR.collection.safe_clone = function(e, recursive)
606 if (e.nodeName == "#text")
608 return document.createTextNode(e.data);
610 if(!e.tagName) return false;
612 var newNode = document.createElement(e.tagName);
613 if (!newNode) return false;
615 var properties = [ 'id', 'class', 'style', 'name', 'type', 'valign', 'border', 'width', 'height', 'top', 'bottom', 'left', 'right', 'scope', 'row', 'columns', 'src', 'href', 'className', 'align', 'nowrap'];
617 //clee. - Bug: 44976 - IE7 just does not calculate height properties correctly for input elements
618 if(SUGAR.isIE7 && e.tagName.toLowerCase() == 'input')
620 var properties = [ 'id', 'class', 'style', 'name', 'type', 'valign', 'border', 'width', 'top', 'bottom', 'left', 'right', 'scope', 'row', 'columns', 'src', 'href', 'className', 'align', 'nowrap'];
623 for (var i in properties)
625 if (e[properties[i]])
627 //There are two groups of conditional checks here:
628 //The first group is to ignore the style and type attributes for IE browsers
629 //The second group is to ensure that only <a> and <iframe> tags have href attribute
630 if ((properties[i] != 'style' || !SUGAR.isIE) &&
631 //Only <a> and <iframe> tags can have hrefs
632 (properties[i] != 'href' || e.tagName == 'a' || e.tagName == 'iframe')) {
633 if(properties[i] == "type") {
634 newNode.setAttribute(properties[i], e[properties[i]]);
636 newNode[properties[i]] = e[properties[i]];
643 for (var i in e.childNodes)
645 if(e.childNodes[i].nodeName && (!e.className || e.className != "yui-ac-container"))
647 var child = SUGAR.collection.safe_clone(e.childNodes[i], true);
648 if (child) newNode.appendChild(child);