Merge remote-tracking branch 'upstream/4.4' into 4.4-master-2014-01-27-22-52-52
[civicrm-core.git] / js / jquery / jquery.crmeditable.js
1 /**
2 * Copyright (C) 2012 Xavier Dutoit
3 * Licensed to CiviCRM under the Academic Free License version 3.0.
4 *
5 *
6 * This offers two features:
7 * - crmEditable() edit in place of a single field
8 * (mostly a wrapper that binds jeditable features with the ajax api and replies on crm-entity crmf-{field} html conventions)
9 * if you want to add an edit in place on a template:
10 * - add a class crm-entity and id {EntityName}-{Entityid} higher in the dom
11 * - add a class crm-editable and crmf-{FieldName} around the field (you can add a span if needed)
12 * - add data-action=create if you need to specify the api action to call (default setvalue)
13 * crmf- stands for crm field
14 * - crmForm()
15 * this embed a civicrm form and make it in place (load+ajaxForm)
16 * to make it easier to customize the form (eg. hide a button...) it triggers a 'load' event on the form. you can then catch the load on your code (using the $('#id_of_the_form').on(function(){//do something
17 */
18
19 (function($) {
20
21 $.fn.crmEditableEntity = function() {
22 var
23 el = this[0],
24 ret = {},
25 $row = this.first().closest('.crm-entity');
26 ret.entity = $row.data('entity') || $row[0].id.split('-')[0];
27 ret.id = $row.data('id') || $row[0].id.split('-')[1];
28 if (!ret.entity || !ret.id) {
29 return false;
30 }
31 $('.crm-editable, [data-field]', $row).each(function() {
32 var fieldName = $(this).data('field') || this.className.match(/crmf-(\S*)/)[1];
33 if (fieldName) {
34 ret[fieldName] = $(this).text();
35 if (this === el) {
36 ret.field = fieldName;
37 }
38 }
39 });
40 return ret;
41 };
42
43 $.fn.crmEditable = function (options) {
44 var checkable = function () {
45 $(this).change (function() {
46 var info = $(this).crmEditableEntity();
47 if (!info.field) {
48 return false;
49 }
50 var checked = $(this).is(':checked');
51 var params = {
52 sequential: 1,
53 id: info.id,
54 field: info.field,
55 value: checked ? 1 : 0
56 };
57 CRM.api(info.entity, 'setvalue', params, {
58 context: this,
59 error: function (data) {
60 editableSettings.error.call(this, info.entity, info.field, checked, data);
61 },
62 success: function (data) {
63 editableSettings.success.call(this, info.entity, info.field, checked, data);
64 }
65 });
66 });
67 };
68
69 var defaults = {
70 form:{},
71 callBack:function(data){
72 if (data.is_error) {
73 editableSettings.error.call (this,data);
74 } else {
75 return editableSettings.success.call (this,data);
76 }
77 },
78 error: function(entity,field,value,data) {
79 $(this).crmError(data.error_message, ts('Error'));
80 $(this).removeClass('crm-editable-saving');
81 },
82 success: function(entity,field,value,data) {
83 var $i = $(this);
84 CRM.alert('', ts('Saved'), 'success');
85 $i.removeClass ('crm-editable-saving crm-error');
86 $i.html(value);
87 }
88 }
89
90 var editableSettings = $.extend({}, defaults, options);
91 return this.each(function() {
92 var $i = $(this);
93 var fieldName = "";
94
95 if (this.nodeName == "INPUT" && this.type=="checkbox") {
96 checkable.call(this,this);
97 return;
98 }
99
100 if (this.nodeName == 'A') {
101 if (this.className.indexOf('crmf-') == -1) { // it isn't a jeditable field
102 var formSettings= $.extend({}, editableSettings.form ,
103 {source: $i.attr('href')
104 ,success: function (result) {
105 if ($i.hasClass('crm-dialog')) {
106 $('.ui-dialog').dialog('close').remove();
107 } else
108 $i.next().slideUp().remove();
109 $i.trigger('success',result);
110 }
111 });
112 var id= $i.closest('.crm-entity').attr('id');
113 if (id) {
114 var e=id.match(/(\S*)-(\S*)/);
115 if (!e)
116 console && console.log && console.log("Couldn't get the entity id. You need to set class='crm-entity' id='{entityName}-{id}'");
117 formSettings.entity=e[1];
118 formSettings.id=e[2];
119 }
120 if ($i.hasClass('crm-dialog')) {
121 $i.click (function () {
122 var $n=$('<div>Loading</div>').appendTo('body');
123 $n.dialog ({modal:true,width:500});
124 $n.crmForm (formSettings);
125 return false;
126 });
127 } else {
128 $i.click (function () {
129 var $n=$i.next();
130 if (!$n.hasClass('crm-target')) {
131 $n=$i.after('<div class="crm-target"></div>').next();
132 } else {
133 $n.slideToggle();
134 return false;
135 };
136 $n.crmForm (formSettings);
137 return false;
138 });
139 }
140 return;
141 }
142 }
143
144
145 var settings = {
146 tooltip : 'Click to edit...',
147 placeholder : '<span class="crm-editable-placeholder">Click to edit</span>',
148 data: function(value, settings) {
149 return value.replace(/<(?:.|\n)*?>/gm, '');
150 }
151 };
152 if ($i.data('placeholder')) {
153 settings.placeholder = $i.data('placeholder');
154 } else {
155 settings.placeholder = '<span class="crm-editable-placeholder">Click to edit</span>';
156 }
157 if ($i.data('tooltip')) {
158 settings.placeholder = $i.data('tooltip')
159 } else {
160 settings.tooltip = 'Click to edit...';
161 }
162 if ($i.data('type')) {
163 settings.type = $i.data('type');
164 settings.onblur = 'submit';
165 }
166 if ($i.data('options')){
167 settings.data = $i.data('options');
168 }
169 if(settings.type == 'textarea'){
170 $i.addClass ('crm-editable-textarea-enabled');
171 }
172 else{
173 $i.addClass ('crm-editable-enabled');
174 }
175
176 $i.editable(function(value,settings) {
177 $i.addClass ('crm-editable-saving');
178 var
179 info = $i.crmEditableEntity(),
180 params= {},
181 action = $i.data('action') || 'setvalue';
182 if (!info.field) {
183 return false;
184 }
185 if (info.id && info.id !== 'new') {
186 params.id = info.id;
187 }
188 if (action === 'setvalue') {
189 params.field = info.field;
190 params.value = value;
191 }
192 else {
193 params[info.field] = value;
194 }
195 CRM.api(info.entity, action, params, {
196 context: this,
197 error: function (data) {
198 editableSettings.error.call(this, info.entity, info.field, value, data);
199 },
200 success: function (data) {
201 if ($i.data('options')){
202 value = $i.data('options')[value];
203 }
204 editableSettings.success.call(this, info.entity, info.field, value, data);
205 }
206 });
207 },settings);
208 });
209 }
210
211 $.fn.crmForm = function (options ) {
212 var settings = $.extend( {
213 'title':'',
214 'entity':'',
215 'action':'get',
216 'id':0,
217 'sequential':1,
218 'dialog': false,
219 'load' : function (target){},
220 'success' : function (result) {
221 $(this).html(ts('Saved'));
222 }
223 }, options);
224
225
226 return this.each(function() {
227 var formLoaded = function (target) {
228 var $this =$(target);
229 var destination="<input type='hidden' name='civicrmDestination' value='"+CRM.url('civicrm/ajax/rest',{
230 'sequential':settings.sequential,
231 'json':'html',
232 'entity':settings.entity,
233 'action':settings.action,
234 'id':settings.id
235 })+"' />";
236 $this.find('form').ajaxForm({
237 beforeSubmit :function () {
238 $this.html("<div class='crm-editable-saving'>Saving...</div>");
239 return true;
240 },
241 success:function(response) {
242 if (response.indexOf('crm-error') >= 0) { // we got an error, re-display the page
243 $this.html(response);
244 formLoaded(target);
245 } else {
246 if (response[0] == '{')
247 settings.success($.parseJSON (response));
248 else
249 settings.success(response);
250 }
251 }
252 }).append('<input type="hidden" name="snippet" value="1"/>'+destination).trigger('load');
253
254 settings.load(target);
255 };
256
257 var $this = $(this);
258 if (settings.source && settings.source.indexOf('snippet') == -1) {
259 if (settings.source.indexOf('?') >= 0)
260 settings.source = settings.source + "&snippet=1";
261 else
262 settings.source = settings.source + "?snippet=1";
263 }
264
265
266 $this.html ("Loading...");
267 if (settings.dialog)
268 $this.dialog({width:'auto',minWidth:600});
269 $this.load (settings.source ,function (){formLoaded(this)});
270
271 });
272 };
273
274 })(jQuery);