1/* 2 * Copyright (C) 2008 Apple Inc. All Rights Reserved. 3 * Copyright (C) 2009 Joseph Pecoraro 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY 15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR 18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 */ 26 27WebInspector.ObjectPropertiesSection = function(object, title, subtitle, emptyPlaceholder, ignoreHasOwnProperty, extraProperties, treeElementConstructor) 28{ 29 this.emptyPlaceholder = (emptyPlaceholder || WebInspector.UIString("No Properties")); 30 this.object = object; 31 this.ignoreHasOwnProperty = ignoreHasOwnProperty; 32 this.extraProperties = extraProperties; 33 this.treeElementConstructor = treeElementConstructor || WebInspector.ObjectPropertyTreeElement; 34 this.editable = true; 35 36 WebInspector.PropertiesSection.call(this, title, subtitle); 37} 38 39WebInspector.ObjectPropertiesSection.prototype = { 40 onpopulate: function() 41 { 42 this.update(); 43 }, 44 45 update: function() 46 { 47 var self = this; 48 var callback = function(properties) { 49 if (!properties) 50 return; 51 self._update(properties); 52 }; 53 InspectorController.getProperties(this.object, this.ignoreHasOwnProperty, callback); 54 }, 55 56 _update: function(properties) 57 { 58 if (this.extraProperties) 59 for (var prop in this.extraProperties) 60 properties.push(new WebInspector.ObjectPropertyProxy(prop, this.extraProperties[prop])); 61 properties.sort(this._displaySort); 62 63 this.propertiesTreeOutline.removeChildren(); 64 65 for (var i = 0; i < properties.length; ++i) 66 this.propertiesTreeOutline.appendChild(new this.treeElementConstructor(properties[i])); 67 68 if (!this.propertiesTreeOutline.children.length) { 69 var title = "<div class=\"info\">" + this.emptyPlaceholder + "</div>"; 70 var infoElement = new TreeElement(title, null, false); 71 this.propertiesTreeOutline.appendChild(infoElement); 72 } 73 }, 74 75 _displaySort: function(propertyA, propertyB) { 76 var a = propertyA.name; 77 var b = propertyB.name; 78 79 // if used elsewhere make sure to 80 // - convert a and b to strings (not needed here, properties are all strings) 81 // - check if a == b (not needed here, no two properties can be the same) 82 83 var diff = 0; 84 var chunk = /^\d+|^\D+/; 85 var chunka, chunkb, anum, bnum; 86 while (diff === 0) { 87 if (!a && b) 88 return -1; 89 if (!b && a) 90 return 1; 91 chunka = a.match(chunk)[0]; 92 chunkb = b.match(chunk)[0]; 93 anum = !isNaN(chunka); 94 bnum = !isNaN(chunkb); 95 if (anum && !bnum) 96 return -1; 97 if (bnum && !anum) 98 return 1; 99 if (anum && bnum) { 100 diff = chunka - chunkb; 101 if (diff === 0 && chunka.length !== chunkb.length) { 102 if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case) 103 return chunka.length - chunkb.length; 104 else 105 return chunkb.length - chunka.length; 106 } 107 } else if (chunka !== chunkb) 108 return (chunka < chunkb) ? -1 : 1; 109 a = a.substring(chunka.length); 110 b = b.substring(chunkb.length); 111 } 112 return diff; 113 } 114} 115 116WebInspector.ObjectPropertiesSection.prototype.__proto__ = WebInspector.PropertiesSection.prototype; 117 118WebInspector.ObjectPropertyTreeElement = function(property) 119{ 120 this.property = property; 121 122 // Pass an empty title, the title gets made later in onattach. 123 TreeElement.call(this, "", null, false); 124} 125 126WebInspector.ObjectPropertyTreeElement.prototype = { 127 safePropertyValue: function(object, propertyName) 128 { 129 if (object["__lookupGetter__"] && object.__lookupGetter__(propertyName)) 130 return; 131 return object[propertyName]; 132 }, 133 134 onpopulate: function() 135 { 136 if (this.children.length && !this.shouldRefreshChildren) 137 return; 138 139 var self = this; 140 var callback = function(properties) { 141 self.removeChildren(); 142 if (!properties) 143 return; 144 145 properties.sort(self._displaySort); 146 for (var i = 0; i < properties.length; ++i) { 147 self.appendChild(new self.treeOutline.section.treeElementConstructor(properties[i])); 148 } 149 }; 150 InspectorController.getProperties(this.property.childObjectProxy, false, callback); 151 }, 152 153 ondblclick: function(element, event) 154 { 155 this.startEditing(); 156 }, 157 158 onattach: function() 159 { 160 this.update(); 161 }, 162 163 update: function() 164 { 165 var nameElement = document.createElement("span"); 166 nameElement.className = "name"; 167 nameElement.textContent = this.property.name; 168 169 this.valueElement = document.createElement("span"); 170 this.valueElement.className = "value"; 171 this.valueElement.textContent = this.property.textContent; 172 if (this.property.isGetter) 173 this.valueElement.addStyleClass("dimmed"); 174 175 this.listItemElement.removeChildren(); 176 177 this.listItemElement.appendChild(nameElement); 178 this.listItemElement.appendChild(document.createTextNode(": ")); 179 this.listItemElement.appendChild(this.valueElement); 180 this.hasChildren = this.property.hasChildren; 181 }, 182 183 updateSiblings: function() 184 { 185 if (this.parent.root) 186 this.treeOutline.section.update(); 187 else 188 this.parent.shouldRefreshChildren = true; 189 }, 190 191 startEditing: function() 192 { 193 if (WebInspector.isBeingEdited(this.valueElement) || !this.treeOutline.section.editable) 194 return; 195 196 var context = { expanded: this.expanded }; 197 198 // Lie about our children to prevent expanding on double click and to collapse subproperties. 199 this.hasChildren = false; 200 201 this.listItemElement.addStyleClass("editing-sub-part"); 202 203 WebInspector.startEditing(this.valueElement, this.editingCommitted.bind(this), this.editingCancelled.bind(this), context); 204 }, 205 206 editingEnded: function(context) 207 { 208 this.listItemElement.scrollLeft = 0; 209 this.listItemElement.removeStyleClass("editing-sub-part"); 210 if (context.expanded) 211 this.expand(); 212 }, 213 214 editingCancelled: function(element, context) 215 { 216 this.update(); 217 this.editingEnded(context); 218 }, 219 220 editingCommitted: function(element, userInput, previousContent, context) 221 { 222 if (userInput === previousContent) 223 return this.editingCancelled(element, context); // nothing changed, so cancel 224 225 this.applyExpression(userInput, true); 226 227 this.editingEnded(context); 228 }, 229 230 applyExpression: function(expression, updateInterface) 231 { 232 expression = expression.trimWhitespace(); 233 var expressionLength = expression.length; 234 var self = this; 235 var callback = function(success) { 236 if (!updateInterface) 237 return; 238 239 if (!success) 240 self.update(); 241 242 if (!expressionLength) { 243 // The property was deleted, so remove this tree element. 244 self.parent.removeChild(this); 245 } else { 246 // Call updateSiblings since their value might be based on the value that just changed. 247 self.updateSiblings(); 248 } 249 }; 250 InspectorController.setPropertyValue(this.property.parentObjectProxy, this.property.name, expression.trimWhitespace(), callback); 251 } 252} 253 254WebInspector.ObjectPropertyTreeElement.prototype.__proto__ = TreeElement.prototype; 255