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