Changed Roo/HtmlEditorCore.jsRoo/form/BasicForm.jsRoo/form/ComboBoxArray.js
[roojs1] / Roo / form / BasicForm.js
1 /*
2  * Based on:
3  * Ext JS Library 1.1.1
4  * Copyright(c) 2006-2007, Ext JS, LLC.
5  *
6  * Originally Released Under LGPL - original licence link has changed is not relivant.
7  *
8  * Fork - LGPL
9  * <script type="text/javascript">
10  */
11  
12 /**
13  * @class Roo.form.BasicForm
14  * @extends Roo.util.Observable
15  * Supplies the functionality to do "actions" on forms and initialize Roo.form.Field types on existing markup.
16  * @constructor
17  * @param {String/HTMLElement/Roo.Element} el The form element or its id
18  * @param {Object} config Configuration options
19  */
20 Roo.form.BasicForm = function(el, config){
21     this.allItems = [];
22     this.childForms = [];
23     Roo.apply(this, config);
24     /*
25      * The Roo.form.Field items in this form.
26      * @type MixedCollection
27      */
28      
29      
30     this.items = new Roo.util.MixedCollection(false, function(o){
31         return o.id || (o.id = Roo.id());
32     });
33     this.addEvents({
34         /**
35          * @event beforeaction
36          * Fires before any action is performed. Return false to cancel the action.
37          * @param {Form} this
38          * @param {Action} action The action to be performed
39          */
40         beforeaction: true,
41         /**
42          * @event actionfailed
43          * Fires when an action fails.
44          * @param {Form} this
45          * @param {Action} action The action that failed
46          */
47         actionfailed : true,
48         /**
49          * @event actioncomplete
50          * Fires when an action is completed.
51          * @param {Form} this
52          * @param {Action} action The action that completed
53          */
54         actioncomplete : true
55     });
56     if(el){
57         this.initEl(el);
58     }
59     Roo.form.BasicForm.superclass.constructor.call(this);
60     
61     Roo.form.BasicForm.popover.apply();
62 };
63
64 Roo.extend(Roo.form.BasicForm, Roo.util.Observable, {
65     /**
66      * @cfg {String} method
67      * The request method to use (GET or POST) for form actions if one isn't supplied in the action options.
68      */
69     /**
70      * @cfg {DataReader} reader
71      * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when executing "load" actions.
72      * This is optional as there is built-in support for processing JSON.
73      */
74     /**
75      * @cfg {DataReader} errorReader
76      * An Roo.data.DataReader (e.g. {@link Roo.data.XmlReader}) to be used to read data when reading validation errors on "submit" actions.
77      * This is completely optional as there is built-in support for processing JSON.
78      */
79     /**
80      * @cfg {String} url
81      * The URL to use for form actions if one isn't supplied in the action options.
82      */
83     /**
84      * @cfg {Boolean} fileUpload
85      * Set to true if this form is a file upload.
86      */
87      
88     /**
89      * @cfg {Object} baseParams
90      * Parameters to pass with all requests. e.g. baseParams: {id: '123', foo: 'bar'}.
91      */
92      /**
93      
94     /**
95      * @cfg {Number} timeout Timeout for form actions in seconds (default is 30 seconds).
96      */
97     timeout: 30,
98
99     // private
100     activeAction : null,
101
102     /**
103      * @cfg {Boolean} trackResetOnLoad If set to true, form.reset() resets to the last loaded
104      * or setValues() data instead of when the form was first created.
105      */
106     trackResetOnLoad : false,
107     
108     
109     /**
110      * childForms - used for multi-tab forms
111      * @type {Array}
112      */
113     childForms : false,
114     
115     /**
116      * allItems - full list of fields.
117      * @type {Array}
118      */
119     allItems : false,
120     
121     /**
122      * By default wait messages are displayed with Roo.MessageBox.wait. You can target a specific
123      * element by passing it or its id or mask the form itself by passing in true.
124      * @type Mixed
125      */
126     waitMsgTarget : false,
127     
128     /**
129      * @type Boolean
130      */
131     disableMask : false,
132     
133     /**
134      * @cfg {Boolean} errorMask (true|false) default false
135      */
136     errorMask : false,
137     
138     /**
139      * @cfg {Number} maskOffset Default 100
140      */
141     maskOffset : 100,
142
143     // private
144     initEl : function(el){
145         this.el = Roo.get(el);
146         this.id = this.el.id || Roo.id();
147         this.el.on('submit', this.onSubmit, this);
148         this.el.addClass('x-form');
149     },
150
151     // private
152     onSubmit : function(e){
153         e.stopEvent();
154     },
155
156     /**
157      * Returns true if client-side validation on the form is successful.
158      * @return Boolean
159      */
160     isValid : function(){
161         var valid = true;
162         var target = false;
163         this.items.each(function(f){
164             if(f.validate()){
165                 return;
166             }
167             
168             valid = false;
169                 
170             if(!target && f.el.isVisible(true)){
171                 target = f;
172             }
173         });
174         
175         if(this.errorMask && !valid){
176             Roo.form.BasicForm.popover.mask(this, target);
177         }
178         
179         return valid;
180     },
181
182     /**
183      * DEPRICATED Returns true if any fields in this form have changed since their original load. 
184      * @return Boolean
185      */
186     isDirty : function(){
187         var dirty = false;
188         this.items.each(function(f){
189            if(f.isDirty()){
190                dirty = true;
191                return false;
192            }
193         });
194         return dirty;
195     },
196     
197     /**
198      * Returns true if any fields in this form have changed since their original load. (New version)
199      * @return Boolean
200      */
201     
202     hasChanged : function()
203     {
204         var dirty = false;
205         this.items.each(function(f){
206            if(f.hasChanged()){
207                dirty = true;
208                return false;
209            }
210         });
211         return dirty;
212         
213     },
214     /**
215      * Resets all hasChanged to 'false' -
216      * The old 'isDirty' used 'original value..' however this breaks reset() and a few other things.
217      * So hasChanged storage is only to be used for this purpose
218      * @return Boolean
219      */
220     resetHasChanged : function()
221     {
222         this.items.each(function(f){
223            f.resetHasChanged();
224         });
225         
226     },
227     
228     
229     /**
230      * Performs a predefined action (submit or load) or custom actions you define on this form.
231      * @param {String} actionName The name of the action type
232      * @param {Object} options (optional) The options to pass to the action.  All of the config options listed
233      * below are supported by both the submit and load actions unless otherwise noted (custom actions could also
234      * accept other config options):
235      * <pre>
236 Property          Type             Description
237 ----------------  ---------------  ----------------------------------------------------------------------------------
238 url               String           The url for the action (defaults to the form's url)
239 method            String           The form method to use (defaults to the form's method, or POST if not defined)
240 params            String/Object    The params to pass (defaults to the form's baseParams, or none if not defined)
241 clientValidation  Boolean          Applies to submit only.  Pass true to call form.isValid() prior to posting to
242                                    validate the form on the client (defaults to false)
243      * </pre>
244      * @return {BasicForm} this
245      */
246     doAction : function(action, options){
247         if(typeof action == 'string'){
248             action = new Roo.form.Action.ACTION_TYPES[action](this, options);
249         }
250         if(this.fireEvent('beforeaction', this, action) !== false){
251             this.beforeAction(action);
252             action.run.defer(100, action);
253         }
254         return this;
255     },
256
257     /**
258      * Shortcut to do a submit action.
259      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
260      * @return {BasicForm} this
261      */
262     submit : function(options){
263         this.doAction('submit', options);
264         return this;
265     },
266
267     /**
268      * Shortcut to do a load action.
269      * @param {Object} options The options to pass to the action (see {@link #doAction} for details)
270      * @return {BasicForm} this
271      */
272     load : function(options){
273         this.doAction('load', options);
274         return this;
275     },
276
277     /**
278      * Persists the values in this form into the passed Roo.data.Record object in a beginEdit/endEdit block.
279      * @param {Record} record The record to edit
280      * @return {BasicForm} this
281      */
282     updateRecord : function(record){
283         record.beginEdit();
284         var fs = record.fields;
285         fs.each(function(f){
286             var field = this.findField(f.name);
287             if(field){
288                 record.set(f.name, field.getValue());
289             }
290         }, this);
291         record.endEdit();
292         return this;
293     },
294
295     /**
296      * Loads an Roo.data.Record into this form.
297      * @param {Record} record The record to load
298      * @return {BasicForm} this
299      */
300     loadRecord : function(record){
301         this.setValues(record.data);
302         return this;
303     },
304
305     // private
306     beforeAction : function(action){
307         var o = action.options;
308         
309         if(!this.disableMask) {
310             if(this.waitMsgTarget === true){
311                 this.el.mask(o.waitMsg || "Sending", 'x-mask-loading');
312             }else if(this.waitMsgTarget){
313                 this.waitMsgTarget = Roo.get(this.waitMsgTarget);
314                 this.waitMsgTarget.mask(o.waitMsg || "Sending", 'x-mask-loading');
315             }else {
316                 Roo.MessageBox.wait(o.waitMsg || "Sending", o.waitTitle || this.waitTitle || 'Please Wait...');
317             }
318         }
319         
320          
321     },
322
323     // private
324     afterAction : function(action, success){
325         this.activeAction = null;
326         var o = action.options;
327         
328         if(!this.disableMask) {
329             if(this.waitMsgTarget === true){
330                 this.el.unmask();
331             }else if(this.waitMsgTarget){
332                 this.waitMsgTarget.unmask();
333             }else{
334                 Roo.MessageBox.updateProgress(1);
335                 Roo.MessageBox.hide();
336             }
337         }
338         
339         if(success){
340             if(o.reset){
341                 this.reset();
342             }
343             Roo.callback(o.success, o.scope, [this, action]);
344             this.fireEvent('actioncomplete', this, action);
345             
346         }else{
347             
348             // failure condition..
349             // we have a scenario where updates need confirming.
350             // eg. if a locking scenario exists..
351             // we look for { errors : { needs_confirm : true }} in the response.
352             if (
353                 (typeof(action.result) != 'undefined')  &&
354                 (typeof(action.result.errors) != 'undefined')  &&
355                 (typeof(action.result.errors.needs_confirm) != 'undefined')
356            ){
357                 var _t = this;
358                 Roo.MessageBox.confirm(
359                     "Change requires confirmation",
360                     action.result.errorMsg,
361                     function(r) {
362                         if (r != 'yes') {
363                             return;
364                         }
365                         _t.doAction('submit', { params :  { _submit_confirmed : 1 } }  );
366                     }
367                     
368                 );
369                 
370                 
371                 
372                 return;
373             }
374             
375             Roo.callback(o.failure, o.scope, [this, action]);
376             // show an error message if no failed handler is set..
377             if (!this.hasListener('actionfailed')) {
378                 Roo.MessageBox.alert("Error",
379                     (typeof(action.result) != 'undefined' && typeof(action.result.errorMsg) != 'undefined') ?
380                         action.result.errorMsg :
381                         "Saving Failed, please check your entries or try again"
382                 );
383             }
384             
385             this.fireEvent('actionfailed', this, action);
386         }
387         
388     },
389
390     /**
391      * Find a Roo.form.Field in this form by id, dataIndex, name or hiddenName
392      * @param {String} id The value to search for
393      * @return Field
394      */
395     findField : function(id){
396         var field = this.items.get(id);
397         if(!field){
398             this.items.each(function(f){
399                 if(f.isFormField && (f.dataIndex == id || f.id == id || f.getName() == id)){
400                     field = f;
401                     return false;
402                 }
403             });
404         }
405         return field || null;
406     },
407
408     /**
409      * Add a secondary form to this one, 
410      * Used to provide tabbed forms. One form is primary, with hidden values 
411      * which mirror the elements from the other forms.
412      * 
413      * @param {Roo.form.Form} form to add.
414      * 
415      */
416     addForm : function(form)
417     {
418        
419         if (this.childForms.indexOf(form) > -1) {
420             // already added..
421             return;
422         }
423         this.childForms.push(form);
424         var n = '';
425         Roo.each(form.allItems, function (fe) {
426             
427             n = typeof(fe.getName) == 'undefined' ? fe.name : fe.getName();
428             if (this.findField(n)) { // already added..
429                 return;
430             }
431             var add = new Roo.form.Hidden({
432                 name : n
433             });
434             add.render(this.el);
435             
436             this.add( add );
437         }, this);
438         
439     },
440     /**
441      * Mark fields in this form invalid in bulk.
442      * @param {Array/Object} errors Either an array in the form [{id:'fieldId', msg:'The message'},...] or an object hash of {id: msg, id2: msg2}
443      * @return {BasicForm} this
444      */
445     markInvalid : function(errors){
446         if(errors instanceof Array){
447             for(var i = 0, len = errors.length; i < len; i++){
448                 var fieldError = errors[i];
449                 var f = this.findField(fieldError.id);
450                 if(f){
451                     f.markInvalid(fieldError.msg);
452                 }
453             }
454         }else{
455             var field, id;
456             for(id in errors){
457                 if(typeof errors[id] != 'function' && (field = this.findField(id))){
458                     field.markInvalid(errors[id]);
459                 }
460             }
461         }
462         Roo.each(this.childForms || [], function (f) {
463             f.markInvalid(errors);
464         });
465         
466         return this;
467     },
468
469     /**
470      * Set values for fields in this form in bulk.
471      * @param {Array/Object} values Either an array in the form [{id:'fieldId', value:'foo'},...] or an object hash of {id: value, id2: value2}
472      * @return {BasicForm} this
473      */
474     setValues : function(values){
475         if(values instanceof Array){ // array of objects
476             for(var i = 0, len = values.length; i < len; i++){
477                 var v = values[i];
478                 var f = this.findField(v.id);
479                 if(f){
480                     f.setValue(v.value);
481                     if(this.trackResetOnLoad){
482                         f.originalValue = f.getValue();
483                     }
484                 }
485             }
486         }else{ // object hash
487             var field, id;
488             for(id in values){
489                 if(typeof values[id] != 'function' && (field = this.findField(id))){
490                     
491                     if (field.setFromData && 
492                         field.valueField && 
493                         field.displayField &&
494                         // combos' with local stores can 
495                         // be queried via setValue()
496                         // to set their value..
497                         (field.store && !field.store.isLocal)
498                         ) {
499                         // it's a combo
500                         var sd = { };
501                         sd[field.valueField] = typeof(values[field.hiddenName]) == 'undefined' ? '' : values[field.hiddenName];
502                         sd[field.displayField] = typeof(values[field.name]) == 'undefined' ? '' : values[field.name];
503                         field.setFromData(sd);
504                         
505                     } else {
506                         field.setValue(values[id]);
507                     }
508                     
509                     
510                     if(this.trackResetOnLoad){
511                         field.originalValue = field.getValue();
512                     }
513                 }
514             }
515         }
516         this.resetHasChanged();
517         
518         
519         Roo.each(this.childForms || [], function (f) {
520             f.setValues(values);
521             f.resetHasChanged();
522         });
523                 
524         return this;
525     },
526  
527     /**
528      * Returns the fields in this form as an object with key/value pairs. If multiple fields exist with the same name
529      * they are returned as an array.
530      * @param {Boolean} asString
531      * @return {Object}
532      */
533     getValues : function(asString){
534         if (this.childForms) {
535             // copy values from the child forms
536             Roo.each(this.childForms, function (f) {
537                 this.setValues(f.getValues());
538             }, this);
539         }
540         
541         // use formdata
542         if (typeof(FormData) != 'undefined' && asString !== true) {
543             var fd = (new FormData(this.el.dom)).entries();
544             var ret = {};
545             var ent = fd.next();
546             while (!ent.done) {
547                 ret[ent.value[0]] = ent.value[1]; // not sure how this will handle duplicates..
548                 ent = fd.next();
549             };
550             return ret;
551         }
552         
553         
554         var fs = Roo.lib.Ajax.serializeForm(this.el.dom);
555         if(asString === true){
556             return fs;
557         }
558         return Roo.urlDecode(fs);
559     },
560     
561     /**
562      * Returns the fields in this form as an object with key/value pairs. 
563      * This differs from getValues as it calls getValue on each child item, rather than using dom data.
564      * @return {Object}
565      */
566     getFieldValues : function(with_hidden)
567     {
568         if (this.childForms) {
569             // copy values from the child forms
570             // should this call getFieldValues - probably not as we do not currently copy
571             // hidden fields when we generate..
572             Roo.each(this.childForms, function (f) {
573                 this.setValues(f.getValues());
574             }, this);
575         }
576         
577         var ret = {};
578         this.items.each(function(f){
579             if (!f.getName()) {
580                 return;
581             }
582             var v = f.getValue();
583             if (f.inputType =='radio') {
584                 if (typeof(ret[f.getName()]) == 'undefined') {
585                     ret[f.getName()] = ''; // empty..
586                 }
587                 
588                 if (!f.el.dom.checked) {
589                     return;
590                     
591                 }
592                 v = f.el.dom.value;
593                 
594             }
595             
596             // not sure if this supported any more..
597             if ((typeof(v) == 'object') && f.getRawValue) {
598                 v = f.getRawValue() ; // dates..
599             }
600             // combo boxes where name != hiddenName...
601             if (f.name != f.getName()) {
602                 ret[f.name] = f.getRawValue();
603             }
604             ret[f.getName()] = v;
605         });
606         
607         return ret;
608     },
609
610     /**
611      * Clears all invalid messages in this form.
612      * @return {BasicForm} this
613      */
614     clearInvalid : function(){
615         this.items.each(function(f){
616            f.clearInvalid();
617         });
618         
619         Roo.each(this.childForms || [], function (f) {
620             f.clearInvalid();
621         });
622         
623         
624         return this;
625     },
626
627     /**
628      * Resets this form.
629      * @return {BasicForm} this
630      */
631     reset : function(){
632         this.items.each(function(f){
633             f.reset();
634         });
635         
636         Roo.each(this.childForms || [], function (f) {
637             f.reset();
638         });
639         this.resetHasChanged();
640         
641         return this;
642     },
643
644     /**
645      * Add Roo.form components to this form.
646      * @param {Field} field1
647      * @param {Field} field2 (optional)
648      * @param {Field} etc (optional)
649      * @return {BasicForm} this
650      */
651     add : function(){
652         this.items.addAll(Array.prototype.slice.call(arguments, 0));
653         return this;
654     },
655
656
657     /**
658      * Removes a field from the items collection (does NOT remove its markup).
659      * @param {Field} field
660      * @return {BasicForm} this
661      */
662     remove : function(field){
663         this.items.remove(field);
664         return this;
665     },
666
667     /**
668      * Looks at the fields in this form, checks them for an id attribute,
669      * and calls applyTo on the existing dom element with that id.
670      * @return {BasicForm} this
671      */
672     render : function(){
673         this.items.each(function(f){
674             if(f.isFormField && !f.rendered && document.getElementById(f.id)){ // if the element exists
675                 f.applyTo(f.id);
676             }
677         });
678         return this;
679     },
680
681     /**
682      * Calls {@link Ext#apply} for all fields in this form with the passed object.
683      * @param {Object} values
684      * @return {BasicForm} this
685      */
686     applyToFields : function(o){
687         this.items.each(function(f){
688            Roo.apply(f, o);
689         });
690         return this;
691     },
692
693     /**
694      * Calls {@link Ext#applyIf} for all field in this form with the passed object.
695      * @param {Object} values
696      * @return {BasicForm} this
697      */
698     applyIfToFields : function(o){
699         this.items.each(function(f){
700            Roo.applyIf(f, o);
701         });
702         return this;
703     }
704 });
705
706 // back compat
707 Roo.BasicForm = Roo.form.BasicForm;
708
709 Roo.apply(Roo.form.BasicForm, {
710     
711     popover : {
712         
713         padding : 5,
714         
715         isApplied : false,
716         
717         isMasked : false,
718         
719         form : false,
720         
721         target : false,
722         
723         intervalID : false,
724         
725         maskEl : false,
726         
727         apply : function()
728         {
729             if(this.isApplied){
730                 return;
731             }
732             
733             this.maskEl = {
734                 top : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-top-mask" }, true),
735                 left : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-left-mask" }, true),
736                 bottom : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-bottom-mask" }, true),
737                 right : Roo.DomHelper.append(Roo.get(document.body), { tag: "div", cls:"x-dlg-mask roo-form-right-mask" }, true)
738             };
739             
740             this.maskEl.top.enableDisplayMode("block");
741             this.maskEl.left.enableDisplayMode("block");
742             this.maskEl.bottom.enableDisplayMode("block");
743             this.maskEl.right.enableDisplayMode("block");
744             
745             Roo.get(document.body).on('click', function(){
746                 this.unmask();
747             }, this);
748             
749             Roo.get(document.body).on('touchstart', function(){
750                 this.unmask();
751             }, this);
752             
753             this.isApplied = true
754         },
755         
756         mask : function(form, target)
757         {
758             this.form = form;
759             
760             this.target = target;
761             
762             if(!this.form.errorMask || !target.el){
763                 return;
764             }
765             
766             var scrollable = this.target.el.findScrollableParent() || this.target.el.findParent('div.x-layout-active-content', 100, true) || Roo.get(document.body);
767             
768             var ot = this.target.el.calcOffsetsTo(scrollable);
769             
770             var scrollTo = ot[1] - this.form.maskOffset;
771             
772             scrollTo = Math.min(scrollTo, scrollable.dom.scrollHeight);
773             
774             scrollable.scrollTo('top', scrollTo);
775             
776             var el = this.target.wrap || this.target.el;
777             
778             var box = el.getBox();
779             
780             this.maskEl.top.setStyle('position', 'absolute');
781             this.maskEl.top.setStyle('z-index', 10000);
782             this.maskEl.top.setSize(Roo.lib.Dom.getDocumentWidth(), box.y - this.padding);
783             this.maskEl.top.setLeft(0);
784             this.maskEl.top.setTop(0);
785             this.maskEl.top.show();
786             
787             this.maskEl.left.setStyle('position', 'absolute');
788             this.maskEl.left.setStyle('z-index', 10000);
789             this.maskEl.left.setSize(box.x - this.padding, box.height + this.padding * 2);
790             this.maskEl.left.setLeft(0);
791             this.maskEl.left.setTop(box.y - this.padding);
792             this.maskEl.left.show();
793
794             this.maskEl.bottom.setStyle('position', 'absolute');
795             this.maskEl.bottom.setStyle('z-index', 10000);
796             this.maskEl.bottom.setSize(Roo.lib.Dom.getDocumentWidth(), Roo.lib.Dom.getDocumentHeight() - box.bottom - this.padding);
797             this.maskEl.bottom.setLeft(0);
798             this.maskEl.bottom.setTop(box.bottom + this.padding);
799             this.maskEl.bottom.show();
800
801             this.maskEl.right.setStyle('position', 'absolute');
802             this.maskEl.right.setStyle('z-index', 10000);
803             this.maskEl.right.setSize(Roo.lib.Dom.getDocumentWidth() - box.right - this.padding, box.height + this.padding * 2);
804             this.maskEl.right.setLeft(box.right + this.padding);
805             this.maskEl.right.setTop(box.y - this.padding);
806             this.maskEl.right.show();
807
808             this.intervalID = window.setInterval(function() {
809                 Roo.form.BasicForm.popover.unmask();
810             }, 10000);
811
812             window.onwheel = function(){ return false;};
813             
814             (function(){ this.isMasked = true; }).defer(500, this);
815             
816         },
817         
818         unmask : function()
819         {
820             if(!this.isApplied || !this.isMasked || !this.form || !this.target || !this.form.errorMask){
821                 return;
822             }
823             
824             this.maskEl.top.setStyle('position', 'absolute');
825             this.maskEl.top.setSize(0, 0).setXY([0, 0]);
826             this.maskEl.top.hide();
827
828             this.maskEl.left.setStyle('position', 'absolute');
829             this.maskEl.left.setSize(0, 0).setXY([0, 0]);
830             this.maskEl.left.hide();
831
832             this.maskEl.bottom.setStyle('position', 'absolute');
833             this.maskEl.bottom.setSize(0, 0).setXY([0, 0]);
834             this.maskEl.bottom.hide();
835
836             this.maskEl.right.setStyle('position', 'absolute');
837             this.maskEl.right.setSize(0, 0).setXY([0, 0]);
838             this.maskEl.right.hide();
839             
840             window.onwheel = function(){ return true;};
841             
842             if(this.intervalID){
843                 window.clearInterval(this.intervalID);
844                 this.intervalID = false;
845             }
846             
847             this.isMasked = false;
848             
849         }
850         
851     }
852     
853 });