1 sc_require('views/template'); 2 3 /** @private 4 @class 5 6 SC._BindableSpan is a private view created by the Handlebars {{bind}} helpers 7 that is used to keep track of bound properties. 8 9 Every time a property is bound using a {{mustache}}, an anonymous subclass of 10 SC._BindableSpan is created with the appropriate sub-template and context 11 set up. When the associated property changes, just the template for this view 12 will re-render. 13 */ 14 SC._BindableSpan = SC.TemplateView.extend( 15 /** @scope SC._BindableSpan.prototype */{ 16 /** 17 The type of HTML tag to use. To ensure compatibility with 18 Internet Explorer 7, a <span> tag is used to ensure that inline elements are 19 not rendered with display: block. 20 21 @type String 22 */ 23 tagName: 'span', 24 25 /** 26 The function used to determine if the +displayTemplate+ or 27 +inverseTemplate+ should be rendered. This should be a function that takes 28 a value and returns a Boolean. 29 30 @type Function 31 */ 32 shouldDisplayFunc: null, 33 34 /** 35 Whether the template rendered by this view gets passed the context object 36 of its parent template, or gets passed the value of retrieving +property+ 37 from the previous context. 38 39 For example, this is YES when using the {{#if}} helper, because the template 40 inside the helper should look up properties relative to the same object as 41 outside the block. This would be NO when used with +{{#with foo}}+ because 42 the template should receive the object found by evaluating +foo+. 43 44 @type Boolean 45 */ 46 preserveContext: NO, 47 48 /** 49 The template to render when +shouldDisplayFunc+ evaluates to YES. 50 51 @type Function 52 */ 53 displayTemplate: null, 54 55 /** 56 The template to render when +shouldDisplayFunc+ evaluates to NO. 57 58 @type Function 59 */ 60 inverseTemplate: null, 61 62 /** 63 The key to look up on +previousContext+ that is passed to 64 +shouldDisplayFunc+ to determine which template to render. 65 66 In addition, if +preserveContext+ is NO, this object will be passed to the 67 template when rendering. 68 69 @type String 70 */ 71 property: null, 72 73 /** 74 Determines which template to invoke, sets up the correct state based on 75 that logic, then invokes the default SC.TemplateView +render+ 76 implementation. 77 78 This method will first look up the +property+ key on +previousContext+, 79 then pass that value to the +shouldDisplayFunc+ function. If that returns 80 YES, the +displayTemplate+ function will be rendered to DOM. Otherwise, 81 +inverseTemplate+, if specified, will be rendered. 82 83 For example, if this SC._BindableSpan represented the {{#with foo}} helper, 84 it would look up the +foo+ property of its context, and +shouldDisplayFunc+ 85 would always return true. The object found by looking up +foo+ would be 86 passed to +displayTemplate+. 87 88 @param {SC.RenderContext} renderContext} 89 */ 90 render: function(renderContext) { 91 // If not invoked via a triple-mustache ({{{foo}}}), escape 92 // the content of the template. 93 var escape = this.get('isEscaped'); 94 95 var shouldDisplay = this.get('shouldDisplayFunc'), 96 property = this.get('property'), 97 preserveContext = this.get('preserveContext'), 98 context = this.get('previousContext'); 99 100 var inverseTemplate = this.get('inverseTemplate'), 101 displayTemplate = this.get('displayTemplate'); 102 103 var result; 104 105 106 // Use the current context as the result if no 107 // property is provided. 108 if (property === '') { 109 result = context; 110 } else { 111 result = context.getPath(property); 112 } 113 114 // First, test the conditional to see if we should 115 // render the template or not. 116 if (shouldDisplay(result)) { 117 this.set('template', displayTemplate); 118 119 // If we are preserving the context (for example, if this 120 // is an #if block, call the template with the same object. 121 if (preserveContext) { 122 this.set('context', context); 123 } else { 124 // Otherwise, determine if this is a block bind or not. 125 // If so, pass the specified object to the template 126 if (displayTemplate) { 127 this.set('context', result); 128 } else { 129 // This is not a bind block, just push the result of the 130 // expression to the render context and return. 131 if (result == null) { result = ""; } else { result = String(result); } 132 if (escape) { result = Handlebars.Utils.escapeExpression(result); } 133 renderContext.push(result); //Handlebars.Utils.escapeExpression(result)); 134 return; 135 } 136 } 137 } else if (inverseTemplate) { 138 this.set('template', inverseTemplate); 139 140 if (preserveContext) { 141 this.set('context', context); 142 } else { 143 this.set('context', result); 144 } 145 } else { 146 this.set('template', function() { return ''; }); 147 } 148 149 return sc_super(); 150 }, 151 152 /** 153 Called when the property associated with this <span> changes. 154 155 We destroy all registered children, then render the view again and insert 156 it into DOM. 157 */ 158 rerender: function() { 159 var idx, len, childViews, childView; 160 161 childViews = this.get('childViews'); 162 len = childViews.get('length'); 163 for (idx = len-1; idx >= 0; idx--){ 164 childView = childViews[idx]; 165 // childView.$().remove(); 166 // childView.removeFromParent(); 167 childView.destroy(); 168 } 169 170 var context = this.renderContext(this.get('tagName')); 171 var elem; 172 this.renderToContext(context); 173 174 elem = context.element(); 175 this.$().replaceWith(elem); 176 this.set('layer', elem); 177 178 this._sc_addRenderedStateObservers(); 179 this._callOnChildViews('_sc_addRenderedStateObservers'); 180 181 // Notify for each child (that changed state) in reverse so that each child is in the proper 182 // state before its parent potentially alters its state. For example, a parent could modify 183 // children in `didCreateLayer`. 184 this._callOnChildViews('_notifyDidRender', false); 185 this._notifyDidRender(); 186 } 187 }); 188 189