1 // ==========================================================================
  2 // Project:   SproutCore - JavaScript Application Framework
  3 // Copyright: ©2006-2011 Strobe Inc. and contributors.
  4 //            Portions ©2008-2011 Apple Inc. All rights reserved.
  5 // License:   Licensed under MIT license (see license.js)
  6 // ==========================================================================
  7 
  8 /**
  9   @class
 10   Base class for all render delegates.
 11 
 12   You should use SC.RenderDelegate or a subclass of it as the base for all 
 13   of your render delegates. SC.RenderDelegate offers many helper methods 
 14   and can be simpler to subclass between themes than `SC.Object`.
 15 
 16   Creating & Subclassing
 17   ===
 18   You create render delegates just like you create SC.Objects:
 19 
 20       MyTheme.someRenderDelegate = SC.RenderDelegate.create({ ... });
 21 
 22   You can subclass a render delegate and use that:
 23 
 24       MyTheme.RenderDelegate = SC.RenderDelegate.extend({ ... });
 25       MyTheme.someRenderDelegate = MyTheme.RenderDelegate.create({});
 26 
 27   And you can even subclass instances or SC.RenderDelegate:
 28 
 29       MyTheme.someRenderDelegate = SC.RenderDelegate.create({ ... });
 30       MyTheme.otherRenderDelegate = MyTheme.someRenderDelegate.create({ ... });
 31 
 32       // this allows you to subclass another theme's render delegate:
 33       MyTheme.buttonRenderDelegate = SC.BaseTheme.buttonRenderDelegate.create({ ... });
 34 
 35   For render delegates, subclassing and instantiating are the same.
 36 
 37   NOTE: Even though `.extend` and `.create` technically do the same thing, 
 38   convention dictates that you use `.extend` for RenderDelegates that 
 39   will be used primarily as base classes, and `create` for RenderDelegates
 40   that you expect to be instances.
 41 
 42   Rendering and Updating
 43   ===
 44   Render delegates are most commonly used for two things: rendering and updating
 45   DOM representations of controls.
 46 
 47   Render delegates use their `render` and `update` methods to do this:
 48 
 49       render: function(dataSource, context) {
 50         // rendering tasks here
 51         // example:
 52         context.begin('div').addClass('title')
 53           .text(dataSource.get('title')
 54         .end();
 55       },
 56 
 57       update: function(dataSource, jquery) {
 58         // updating tasks here
 59         // example:
 60         jquery.find('.title').text(dataSource.get('title'));
 61       }
 62 
 63   Variables
 64   ===
 65   The data source provides your render delegate with all of the information
 66   needed to render. However, the render delegate's consumer--usually a view--
 67   may need to get information back.
 68 
 69   For example, `SC.AutoResize` resizes controls to fit their text. You can use
 70   it to size a button to fit its title. But it can't just make the button
 71   have the same width as its title: it needs to be a little larger to make room
 72   for the padding to the left and right sides of the title.
 73 
 74   This padding will vary from theme to theme.
 75   
 76   You can specify properties on the render delegate like any other property:
 77 
 78       MyRenderDelegate = SC.RenderDelegate.create({
 79         autoSizePadding: 10
 80         ...
 81       });
 82 
 83   But there are multiple sizes of buttons; shouldn't the padding change as
 84   well? You can add hashes for the various control sizes and override properties:
 85 
 86       SC.RenderDelegate.create({
 87         autoSizePadding: 10,
 88 
 89         'sc-jumbo-size': {
 90           autoResizePadding: 20
 91         }
 92 
 93   For details, see the discussion on size helpers below.
 94 
 95   You can also calculate values for the data source. In this example, we calculate
 96   the autoSizePadding to equal half the data source's height:
 97 
 98       SC.RenderDelegate.create({
 99         autoSizePaddingFor: function(dataSource) {
100           if (dataSource.get('frame')) {
101             return dataSource.get('frame').height / 2;
102           }
103         }
104 
105 
106   When SC.ButtonView tries to get `autoSizePadding`, the render delegate will look for
107   `autoSizePaddingFor`. It will be called if it exists. Otherwise, the property will
108   be looked up like normal.
109 
110   Note: To support multiple sizes, you must also render the class name; see size
111   helper discussion below.
112 
113   Helpers
114   ===
115   SC.RenderDelegate have "helper methods" to assist the rendering process.
116   There are a few built-in helpers, and you can add your own.
117 
118   Slices
119   ----------------------
120   Chance provides the `includeSlices` method to easily slice images for
121   use in the SproutCore theme system.
122 
123       includeSlices(dataSource, context, slices);
124 
125   You can call this to add DOM that matches Chance's `@include slices()`
126   directive. For example:
127 
128       MyTheme.buttonRenderDelegate = SC.RenderDelegate.create({
129         className: 'button',
130         render: function(dataSource, context) {
131           this.includeSlices(dataSource, context, SC.THREE_SLICE);
132         }
133       });
134 
135   DOM elements will be added as necessary for the slices. From your CSS, you
136   can match it like this:
137 
138       $theme.button {
139         @include slices('button.png', $left: 3, $right: 3);
140       }
141 
142   See the Chance documentation at http://guides.sproutcore.com/chance.html
143   for more about Chance's `@include slices` directive.
144 
145   Sizing Helpers
146   -------------------------
147   As discussed previously, you can create hashes of properties for each size. 
148   However, to support sizing, you must render the size's class name.
149 
150   Use the `addSizeClassName` and `updateSizeClassName` methods:
151 
152       SC.RenderDelegate.create({
153         render: function(dataSource, context) {
154           // if you want to include a class name for the control size
155           // so you can style it via CSS, include this line:
156           this.addSizeClassName(dataSource, context);
157 
158           ...
159         },
160 
161         update: function(dataSource, jquery) {
162           // and don't forget to use its companion in update as well:
163           this.updateSizeClassName(dataSource, jquery);
164 
165           ...
166         }
167       });
168 
169   Controls that allow multiple sizes should also be able to automatically choose
170   the correct size based on the `layout` property supplied by the user. To support
171   this, you can add properties to your size hashes:
172 
173       'sc-regular-size': {
174         // to match _only_ 24px-high buttons
175         height: 24,
176 
177         // or, alternatively, to match ones from 22-26:
178         minHeight: 20, maxHeight: 26,
179 
180         // you can do the same for width if you wanted
181         width: 100
182       }
183 
184   The correct size will be calculated automatically when `addSlizeClassName` is
185   called. If the view explicitly supplies a control size, that size will be used;
186   otherwise, it will be calculated automatically based on the properties in your
187   size hash.
188 
189   Adding Custom Helpers
190   ---------------------
191   You can mix your own helpers into this base class by calling 
192   SC.RenderDelegate.mixin; they will be available to all render delegates:
193 
194       SC.RenderDelegate.mixin({
195         myHelperMethod: function(dataSource) { ... }
196       });
197 
198 
199   You can then use the helpers from your render delegates:
200 
201       MyTheme.someRenderDelegate = SC.RenderDelegate.create({
202         className: 'some-thingy',
203         render: function(dataSource, context) {
204           this.myHelperMethod(dataSource);
205         }
206       });
207 
208 
209   By convention, all render delegate methods should take a `dataSource` as 
210   their first argument. If they do any rendering or updating, their second
211   argument should be the `SC.RenderContext` or `jQuery` object to use.
212 
213   In addition, helpers like these are only meant for methods that should
214   be made available to _all_ render delegates. If your method is specific
215   to just one, add it directly; if it is specific to just a few in your
216   own theme, consider just using mixins or subclassing SC.RenderDelegate:
217 
218       // If you use it in a couple of render delegates, perhaps a mixin
219       // would be best:
220       MyTheme.MyRenderHelper = {
221         helper: function(dataSource) {
222           ...
223         }
224       };
225 
226       MyTheme.myRenderDelegate = SC.RenderDelegate.create(MyTheme.MyRenderHelper, {
227         render: function(dataSource, context) { ... }
228       });
229 
230 
231       // If you use it in all render delegates in your theme, perhaps it
232       // would be better to create an entire subclass of
233       // SC.RenderDelegate:
234       MyTheme.RenderDelegate = SC.RenderDelegate.extend({
235         helper: function(dataSource) {
236           ...
237         }
238       });
239 
240       MyTheme.myRenderDelegate = MyTheme.RenderDelegate.create({
241         render: function(dataSource, context) { ... }
242       });
243 
244   Data Sources
245   ===
246   Render delegates get the content to be rendered from their data sources.
247 
248   A data source can be any object, so long as the object implements
249   the following methods:
250 
251   - `get(propertyName)`: Returns a value for a given property.
252   - `didChangeFor(context, propertyName)`: Returns YES if any properties
253     listed have changed since the last time `didChangeFor` was called with
254     the same context.
255 
256   And the following properties (to be accessed through `.get`):
257 
258   - `theme`: The theme being used to render.
259   - `renderState`: An empty hash for the render delegate to save state in.
260     While render delegates are _usually_ completely stateless, there are
261     cases where they may need to save some sort of state.
262 */
263 SC.RenderDelegate = /** @scope SC.RenderDelegate.prototype */{
264   
265   // docs will look more natural if these are all considered instance
266   // methods/properties.
267 
268   /**
269     Creates a new render delegate based on this one. When you want to
270     create a render delegate, you call this:
271    
272         MyTheme.myRenderDelegate = SC.RenderDelegate.create({
273           className: 'my-render-delegate',
274           render: function(dataSource, context) {
275             // your code here...
276           }
277         })
278   */
279   create: function() {
280     var ret = SC.beget(this);
281 
282     var idx, len = arguments.length;
283     for (idx = 0; idx < len; idx++) {
284       ret.mixin(arguments[idx]);
285     }
286 
287     return ret;
288   },
289 
290   /**
291     Adds extra capabilities to this render delegate.
292    
293     You can use this to add helpers to all render delegates:
294    
295         SC.RenderDelegate.reopen({
296           myHelperMethod: function(dataSource) { ... }
297         });
298    
299   */
300   reopen: function(mixin) {
301     var i, v;
302     for (i in mixin) {
303       v = mixin[i];
304       if (!mixin.hasOwnProperty(i)) {
305         continue;
306       }
307 
308       if (typeof v === 'function' && v !== this[i]) {
309         v.base = this[i] || SC.K;
310       }
311 
312       if (v && v.isEnhancement && v !== this[i]) {
313         v = SC._enhance(this[i] || SC.K, v);
314       }
315 
316       this[i] = v;
317     }
318   },
319 
320   /**
321     Returns the specified property from this render delegate.
322     Implemented to match SC.Object's API.
323   */
324   get: function(propertyName) { return this[propertyName]; },
325 
326   /**
327     Gets or generates the named property for the specified
328     dataSource. If a method `propertyName + 'For'` is found,
329     it will be used to compute the value, `dataSource`
330     being passed as an argument. Otherwise, it will simply
331     be looked up on the render delegate.
332     
333     NOTE: this implementation is a reference implementation. It
334     is overridden in the sizing code (helpers/sizing.js) to be
335     size-sensitive.
336   */
337   getPropertyFor: function(dataSource, propertyName) {
338     if (this[propertyName + 'For']) {
339       return this[propertyName + 'For'](dataSource, propertyName);
340     }
341 
342     return this[propertyName];
343   },
344 
345   /**
346     All render delegates should have a class name. Any time a render delegate is
347     used, this name should be added as a class name (`SC.View`s do this
348     automatically).
349   */
350   className: undefined,
351 
352   /**
353     Writes the DOM representation of this render delegate to the
354     supplied `SC.RenderContext`, using the supplied `dataSource`
355     for any data needed.
356     
357     @method
358     @param {DataSource} dataSource An object from which to get
359     data. See documentation on data sources above.
360     @param {SC.RenderContext} context A context to render DOM into.
361   */
362   render: function(dataSource, context) {
363 
364   },
365 
366   /**
367     Updates the DOM representation of this render delegate using
368     the supplied `jQuery` instance and `dataSource`.
369     
370     @method
371     @param {DataSource} dataSource An object from which to get
372     data. See documentation on data sources above.
373     @param {jQuery} jquery A jQuery instance containing the DOM
374     element to update. This will be the DOM generated by `render()`.
375   */
376   update: function(dataSource, jQuery) {
377 
378   }
379 };
380 
381 // create and extend are technically identical.
382 SC.RenderDelegate.extend = SC.RenderDelegate.create;
383 
384 // and likewise, as this is both a class and an instance, mixin makes
385 // sense instead of reopen...
386 SC.RenderDelegate.mixin = SC.RenderDelegate.reopen;
387