// ========================================================================== // Project: SproutCore - JavaScript Application Framework // Copyright: ©2006-2011 Strobe Inc. and contributors. // Portions ©2008-2011 Apple Inc. All rights reserved. // License: Licensed under MIT license (see license.js) // ==========================================================================

/**

@class
Base class for all render delegates.

You should use SC.RenderDelegate or a subclass of it as the base for all 
of your render delegates. SC.RenderDelegate offers many helper methods 
and can be simpler to subclass between themes than `SC.Object`.

Creating & Subclassing
===
You create render delegates just like you create SC.Objects:

    MyTheme.someRenderDelegate = SC.RenderDelegate.create({ ... });

You can subclass a render delegate and use that:

    MyTheme.RenderDelegate = SC.RenderDelegate.extend({ ... });
    MyTheme.someRenderDelegate = MyTheme.RenderDelegate.create({});

And you can even subclass instances or SC.RenderDelegate:

    MyTheme.someRenderDelegate = SC.RenderDelegate.create({ ... });
    MyTheme.otherRenderDelegate = MyTheme.someRenderDelegate.create({ ... });

    // this allows you to subclass another theme's render delegate:
    MyTheme.buttonRenderDelegate = SC.BaseTheme.buttonRenderDelegate.create({ ... });

For render delegates, subclassing and instantiating are the same.

NOTE: Even though `.extend` and `.create` technically do the same thing, 
convention dictates that you use `.extend` for RenderDelegates that 
will be used primarily as base classes, and `create` for RenderDelegates
that you expect to be instances.

Rendering and Updating
===
Render delegates are most commonly used for two things: rendering and updating
DOM representations of controls.

Render delegates use their `render` and `update` methods to do this:

    render: function(dataSource, context) {
      // rendering tasks here
      // example:
      context.begin('div').addClass('title')
        .text(dataSource.get('title')
      .end();
    },

    update: function(dataSource, jquery) {
      // updating tasks here
      // example:
      jquery.find('.title').text(dataSource.get('title'));
    }

Variables
===
The data source provides your render delegate with all of the information
needed to render. However, the render delegate's consumer--usually a view--
may need to get information back.

For example, `SC.AutoResize` resizes controls to fit their text. You can use
it to size a button to fit its title. But it can't just make the button
have the same width as its title: it needs to be a little larger to make room
for the padding to the left and right sides of the title.

This padding will vary from theme to theme.

You can specify properties on the render delegate like any other property:

    MyRenderDelegate = SC.RenderDelegate.create({
      autoSizePadding: 10
      ...
    });

But there are multiple sizes of buttons; shouldn't the padding change as
well? You can add hashes for the various control sizes and override properties:

    SC.RenderDelegate.create({
      autoSizePadding: 10,

      'sc-jumbo-size': {
        autoResizePadding: 20
      }

For details, see the discussion on size helpers below.

You can also calculate values for the data source. In this example, we calculate
the autoSizePadding to equal half the data source's height:

    SC.RenderDelegate.create({
      autoSizePaddingFor: function(dataSource) {
        if (dataSource.get('frame')) {
          return dataSource.get('frame').height / 2;
        }
      }

When SC.ButtonView tries to get `autoSizePadding`, the render delegate will look for
`autoSizePaddingFor`. It will be called if it exists. Otherwise, the property will
be looked up like normal.

Note: To support multiple sizes, you must also render the class name; see size
helper discussion below.

Helpers
===
SC.RenderDelegate have "helper methods" to assist the rendering process.
There are a few built-in helpers, and you can add your own.

Slices
----------------------
Chance provides the `includeSlices` method to easily slice images for
use in the SproutCore theme system.

    includeSlices(dataSource, context, slices);

You can call this to add DOM that matches Chance's `@include slices()`
directive. For example:

    MyTheme.buttonRenderDelegate = SC.RenderDelegate.create({
      className: 'button',
      render: function(dataSource, context) {
        this.includeSlices(dataSource, context, SC.THREE_SLICE);
      }
    });

DOM elements will be added as necessary for the slices. From your CSS, you
can match it like this:

    $theme.button {
      @include slices('button.png', $left: 3, $right: 3);
    }

See the Chance documentation at http://guides.sproutcore.com/chance.html
for more about Chance's `@include slices` directive.

Sizing Helpers
-------------------------
As discussed previously, you can create hashes of properties for each size. 
However, to support sizing, you must render the size's class name.

Use the `addSizeClassName` and `updateSizeClassName` methods:

    SC.RenderDelegate.create({
      render: function(dataSource, context) {
        // if you want to include a class name for the control size
        // so you can style it via CSS, include this line:
        this.addSizeClassName(dataSource, context);

        ...
      },

      update: function(dataSource, jquery) {
        // and don't forget to use its companion in update as well:
        this.updateSizeClassName(dataSource, jquery);

        ...
      }
    });

Controls that allow multiple sizes should also be able to automatically choose
the correct size based on the `layout` property supplied by the user. To support
this, you can add properties to your size hashes:

    'sc-regular-size': {
      // to match _only_ 24px-high buttons
      height: 24,

      // or, alternatively, to match ones from 22-26:
      minHeight: 20, maxHeight: 26,

      // you can do the same for width if you wanted
      width: 100
    }

The correct size will be calculated automatically when `addSlizeClassName` is
called. If the view explicitly supplies a control size, that size will be used;
otherwise, it will be calculated automatically based on the properties in your
size hash.

Adding Custom Helpers
---------------------
You can mix your own helpers into this base class by calling 
SC.RenderDelegate.mixin; they will be available to all render delegates:

    SC.RenderDelegate.mixin({
      myHelperMethod: function(dataSource) { ... }
    });

You can then use the helpers from your render delegates:

    MyTheme.someRenderDelegate = SC.RenderDelegate.create({
      className: 'some-thingy',
      render: function(dataSource, context) {
        this.myHelperMethod(dataSource);
      }
    });

By convention, all render delegate methods should take a `dataSource` as 
their first argument. If they do any rendering or updating, their second
argument should be the `SC.RenderContext` or `jQuery` object to use.

In addition, helpers like these are only meant for methods that should
be made available to _all_ render delegates. If your method is specific
to just one, add it directly; if it is specific to just a few in your
own theme, consider just using mixins or subclassing SC.RenderDelegate:

    // If you use it in a couple of render delegates, perhaps a mixin
    // would be best:
    MyTheme.MyRenderHelper = {
      helper: function(dataSource) {
        ...
      }
    };

    MyTheme.myRenderDelegate = SC.RenderDelegate.create(MyTheme.MyRenderHelper, {
      render: function(dataSource, context) { ... }
    });

    // If you use it in all render delegates in your theme, perhaps it
    // would be better to create an entire subclass of
    // SC.RenderDelegate:
    MyTheme.RenderDelegate = SC.RenderDelegate.extend({
      helper: function(dataSource) {
        ...
      }
    });

    MyTheme.myRenderDelegate = MyTheme.RenderDelegate.create({
      render: function(dataSource, context) { ... }
    });

Data Sources
===
Render delegates get the content to be rendered from their data sources.

A data source can be any object, so long as the object implements
the following methods:

- `get(propertyName)`: Returns a value for a given property.
- `didChangeFor(context, propertyName)`: Returns YES if any properties
  listed have changed since the last time `didChangeFor` was called with
  the same context.

And the following properties (to be accessed through `.get`):

- `theme`: The theme being used to render.
- `renderState`: An empty hash for the render delegate to save state in.
  While render delegates are _usually_ completely stateless, there are
  cases where they may need to save some sort of state.

*/ SC.RenderDelegate = /** @scope SC.RenderDelegate.prototype */{

// docs will look more natural if these are all considered instance
// methods/properties.

/**
  Creates a new render delegate based on this one. When you want to
  create a render delegate, you call this:

      MyTheme.myRenderDelegate = SC.RenderDelegate.create({
        className: 'my-render-delegate',
        render: function(dataSource, context) {
          // your code here...
        }
      })
*/
create: function() {
  var ret = SC.beget(this);

  var idx, len = arguments.length;
  for (idx = 0; idx < len; idx++) {
    ret.mixin(arguments[idx]);
  }

  return ret;
},

/**
  Adds extra capabilities to this render delegate.

  You can use this to add helpers to all render delegates:

      SC.RenderDelegate.reopen({
        myHelperMethod: function(dataSource) { ... }
      });

*/
reopen: function(mixin) {
  var i, v;
  for (i in mixin) {
    v = mixin[i];
    if (!mixin.hasOwnProperty(i)) {
      continue;
    }

    if (typeof v === 'function' && v !== this[i]) {
      v.base = this[i] || SC.K;
    }

    if (v && v.isEnhancement && v !== this[i]) {
      v = SC._enhance(this[i] || SC.K, v);
    }

    this[i] = v;
  }
},

/**
  Returns the specified property from this render delegate.
  Implemented to match SC.Object's API.
*/
get: function(propertyName) { return this[propertyName]; },

/**
  Gets or generates the named property for the specified
  dataSource. If a method `propertyName + 'For'` is found,
  it will be used to compute the value, `dataSource`
  being passed as an argument. Otherwise, it will simply
  be looked up on the render delegate.

  NOTE: this implementation is a reference implementation. It
  is overridden in the sizing code (helpers/sizing.js) to be
  size-sensitive.
*/
getPropertyFor: function(dataSource, propertyName) {
  if (this[propertyName + 'For']) {
    return this[propertyName + 'For'](dataSource, propertyName);
  }

  return this[propertyName];
},

/**
  All render delegates should have a class name. Any time a render delegate is
  used, this name should be added as a class name (`SC.View`s do this
  automatically).
*/
className: undefined,

/**
  Writes the DOM representation of this render delegate to the
  supplied `SC.RenderContext`, using the supplied `dataSource`
  for any data needed.

  @method
  @param {DataSource} dataSource An object from which to get
  data. See documentation on data sources above.
  @param {SC.RenderContext} context A context to render DOM into.
*/
render: function(dataSource, context) {

},

/**
  Updates the DOM representation of this render delegate using
  the supplied `jQuery` instance and `dataSource`.

  @method
  @param {DataSource} dataSource An object from which to get
  data. See documentation on data sources above.
  @param {jQuery} jquery A jQuery instance containing the DOM
  element to update. This will be the DOM generated by `render()`.
*/
update: function(dataSource, jQuery) {

}

};

// create and extend are technically identical. SC.RenderDelegate.extend = SC.RenderDelegate.create;

// and likewise, as this is both a class and an instance, mixin makes // sense instead of reopen… SC.RenderDelegate.mixin = SC.RenderDelegate.reopen;