我使用的extjs版本是4.1.0 ,在Ext.data.Model中, 有一个属性叫phantom,平时不太用到,也没多注意,直到这2天我们team一个js专家遇到一个很棘手的问题,这才触发我去窥测到这个属性的作用。

 

老习惯,我们还是从需求开始:

需求:

用Extjs的树控件,我们需要在树控件的目录图标(比如 address-service)上双击后,


会创建一个子目录,并且这个子目录的图标前面要有一个"+" 号。

一开始,我们觉得很简单,Controller的代码如下:

this.control({              '#svctree' : {                  beforeitemexpand : function(obj, e) {                     var node =obj.createNode ({text:'asdsdsds',expandable: true});                      obj.appendChild(node,null,true);                  }              },

结果大失所望,我们点击address-service后,新目录(asdsdsds)是创建了,但是前面并没有"+"号:


这是什么原因呢?

 

分析:

费了好大劲,才发现是Ext.data.Model的phantom属性在作怪,这个属性干嘛用的呢?给出官网的说明:

ext-js 中 Ext.data.Model 的 phantom 属性的讨论_第1张图片

我的理解是,这个字段为true,则表示Record是不和存储(store)同步的,也就是脏数据,如果为false,则是同步的

 

联系到我们的例子,后来我又去比较了extjs 4.1.0和以前用过的extjs 4.0.7,终于发现了他们的不同,在于createNode()方法,结论是在extjs 4.1.0中,当调用createNode()来创建树节点时候,它是直接返回的新创建的node对象,这个node对象的phantom属性为true,所以在appendChild()时候,就发生了我们最上面的例子,而在extjs 4.0.7中,它并不直接返回node对象,而是返回NodeInterface decorate()后的node对象,而这个decorate()方法会调用Ext.data.Record的commit()方法,而在这个commit()方法中,它会显式的吧phantom属性设置为false ,所以就不会有我们上述问题了。

 

具体说来:

在extjs 4.1.0中,当执行到createNode()时候,我们跟进看:

ext-js 中 Ext.data.Model 的 phantom 属性的讨论_第2张图片

在extjs-all-debug.js的38490行,它是直接返回node的,而这个node,从右边可以看到,它的值是true.所以出了我们开始的问题。

 

而在extjs 4.0.7中,同样对于createNode,它有着不同的实现,如下53402行所示,它会返回decorate(node)后的结果:

ext-js 中 Ext.data.Model 的 phantom 属性的讨论_第3张图片

而这个NodeInterface 的decorate()方法, 会调用Record的commit()方法,见34行:

Ext.define('Ext.data.NodeInterface', {      requires: ['Ext.data.Field'],       statics: {                    decorate: function(record) {              if (!record.isNode) {                                                      var mgr = Ext.ModelManager,                      modelName = record.modelName,                      modelClass = mgr.getModel(modelName),                      idName = modelClass.prototype.idProperty,                      newFields = [],                      i, newField, len;                                     modelClass.override(this.getPrototypeBody());                  newFields = this.applyFields(modelClass, [  …                  ]);                   len = newFields.length;                                    for (i = 0; i < len; ++i) {                      newField = newFields[i];                      if (record.get(newField.name) === undefined) {                          record.data[newField.name] = newField.defaultValue;                      }                  }              }  ….                            record.commit(true);   …

而Ext.data.Record的commit()方法,会显式的吧phantom属性设置为false,见第7行:

Ext.define('Ext.data.Model', {      alternateClassName: 'Ext.data.Record',  …  commit : function(silent) {          var me = this;           me.phantom = me.dirty = me.editing = false;          me.modified = {};           if (silent !== true) {              me.afterCommit();          }      },   …

 所以,在这种逻辑下,createNode()方法返回的Node的phantom属性就为false ,所以我们接下来在appendChild就不会有任何问题。

 

解决方法:

其实说到这里,很明了了,如果你用的是extjs 4.0.7 ,那么代码就是我最上面的那段代码。如果用的是extjs4.1.0,那么我们必须在appendChild之前吧phantom属性值显式设置为false,如下第5行:

this.control({              '#svctree' : {                  beforeitemexpand : function(obj, e) {                     var node =obj.createNode ({text:'asdsdsds',expandable: true});                  node.phantom=false;                      obj.appendChild(node,null,true);                  }              },

 

最终,因为我们用的是4.1.0,所以我们用了第二种方法,结果如预期所料:

ext-js 中 Ext.data.Model 的 phantom 属性的讨论_第4张图片