Skip to content

Instantly share code, notes, and snippets.

@ialexi
Created October 5, 2010 16:39
Show Gist options
  • Save ialexi/611862 to your computer and use it in GitHub Desktop.
Save ialexi/611862 to your computer and use it in GitHub Desktop.

Chance 2.0

Chance 2.0 is an evolution of Chance. In addition to CSS syntax extensions and automatic image spriting+data urls, Chance 2.0 also covers some changes to SC build tools and bootstrap, and includes a recommendation on how themes should be structured.

Themes Naming

Currently (1.5), there are multiple parts of a theme name:

  • name in the Build tools
    • 'dependencies' in CommonJS
    • :theme => in Ruby
  • name of JS Theme Class. Example: SC.AceTheme.
  • name of theme registered with SC.Theme manager ('sc-ace')
  • class name(s) for the theme.

These shall be replaced with only two names:

  • 'name'
    • used for CSS class names
    • used for build tools
      • JS build tools will check package.json; Ruby tools may require the folder be named something specific.
    • used for registering with SC.Theme manager.
  • JS Class Name

The 'name' may have to be specified in multiple places:

#js
// package.json
{
  'name': 'sc-ace',
  'version': '1.0.0'
}

// theme.js
SC.AceTheme = SC.Theme.extend({
  name: 'sc-ace'
});

But it should be the same name. SC.Theme's classNames property will go away.

Registering Themes

Themes will now be registered through the process of .extend. They will be registered in both class form and instance form. Lines like the following will disappear:

SC.Theme.register('sc-ace', SC.AceTheme);

SC.BaseTheme

If having renderers in BaseTheme as absolutely vital, we should just add them to SC.Theme.prototype. If we don't want to have these default renderers come whether you'd like them or not, then we should move them outside of the SC framework directories and into an actual SC.BaseTheme theme. The reason it is not like this currently is that the tests have difficulty ensuring the theme is loaded, and themes are necessary for views to render.

This is comparatively low-priority cleanup.

Apps & Themes

In addition to having packaged themes (as individual packages in the JS tools or as themes in the themes/ directory of the Ruby tools), each app should have its own theme.

This theme can be in either core.js, or, preferably, a theme.js file:

MyApp.Theme = SC.AceTheme.extend({
  name: 'my-app'
});

SC.defaultTheme = 'my-app';

As the base theme is specified in code, we can drop lines like this from SC:

SC.Pane.prototype.baseTheme = 'sc-ace';

Previously, this hack was used; we were thinking that something could be added to the build tools to switch out a global variable that SC.Pane would then use, but thinking about it more, I think that this approach is much better.

Instead, SC.Pane will use the global 'SC.defaultTheme', which will name the theme to be used for any panes created (unless the panes specify a different theme). This way is much less magical.

The code in SC.BaseTheme should look roughly like this:

// defaults to SC.defaultTheme, which should be set in your App's
// core.js file.
baseTheme: SC.defaultTheme,

Buildfiles: Requiring Themes

In JS build tools, the theme is just another package, and as such, just another dependency in the "dependencies" set.

In the Ruby tools, we should drop :theme => ; the theme should be set programatically in JavaScript.

We only need to :require => it in the buildfile so it may be used. In effect, it should be no different than any other framework. Also, we should be able to require multiple themes and switch between them at run time (optimally string-loading the various renderers as needed).

Theme Structure

Theme structure differs slightly between the JS and the Ruby tools, merely because of package structure differences (if needed, we can implement the Ruby structure in JS or vice-versa).

Ruby:

  • resources/
    • all images and CSS go here. JS is technically allowed here, as well, but should go outside.
  • *.lproj
    • various localization directories, overriding the content of resources/
  • renderers/
    • all renderer JavaScript files go here.

JavaScript:

  • resources/
    • all images and CSS go here. JS won't work if here.
  • lib/
    • all JS goes here.
  • localizations/
    • under here, .lproj folders, which may have underneath:
      • resources/ -- to override CSS and images
      • lib -- to override renderers

There is not a recommended structure for the resources directories EXCEPT that it is best if you end up with one folder per control-size combination. For instance, you may have paths like:

resources/controls/button/44px/button-active.png
resources/controls/button/44px/button.css

A single control/size folder should generally have a single CSS file for that control, and an image for the control in each state or style it has.

If your control has many different sub-themes (for instance, button has point-left, point-right, capsule, etc), a folder structure like this may be more appropriate:

resources/controls/button/capsule/44px/

This may seem like a deep foler structure, but keep in mind that Ace has roughly 30 images just for SC.ButtonView. That's before slicing. Further, one control style (capsule) is only on the 24px sizes of button, and another (the pointer styles) are only on the 24 and 30px variants. If these were all on all 4 sizes, there would be roughly 48 images just to theme SC.ButtonView.

Chance Syntax

Chance will be based on SCSS. Compass libraries will be available for use as well.

The Chance-specific extensions to CSS syntax are:

  • slice/image(): Both actually are the same function; however, slice(), while it makes sense for when you actually are taking a slice out of a larger image, makes less sense when you are just using the image raw.

    The function takes an image name,

  • slices(): Slices a single image into multiple parts using a rectangle identifying the 'middle' of the image; can do 3-slice, 9-slice, and anywhere in between.

    Each slice will be mapped to a class name inside of the selector; for example, if the selector is .button, the left slice will be mapped to .button .left.

  • Debatable: @theme. @theme could make the CSS look a bit more directly related with the JS theme system. However, you can accomplish some of this with native SCSS.

slice()

slice() will be able to act like/be either a function or a mixin, and will take two arguments:

slice(filename[, options])
  • filename: the path to the file relative to the directory of the file using slice()

  • options: key-value argument list, taking the form of:

    $key: value, $key: value

Options:

  • left, top, width, height, right, bottom: define rectangle of the slice to take out of the image named in 'filename'
  • offset: the offset of the slice; if using data: urls, this maps directly to background-position; otherwise, this is added to the offsets calculated by the sprite system.
  • sprite-padding: any padding that should be around the slice should it be sprited. This should be in CSS padding format (minus units).)
  • sprite-anchor: left, right, top, bottom, none; This ensures that the specified side of the slice is touching the border of the sprite image.
  • repeat: repeat-x, repeat-y, repeat-both: this effects both how the slice is sprited (whether it is put into a repeat-x image, repeat-y image, etc.) and the output CSS (background-repeat: ...))

If no set of options are supplied, the set of options from the previous use of 'slice()' in that file will be used; this is because you often want to declare state after state (regular, active, etc.) and it would be tedious to repeat the settings when each image is sliced the same way.

slice() should be called from inside a rule as either a function giving a value for background:, or a mixin that supplies background. In the former, a single string static_url(...) -Xpx -Ypx (repeat-?) will be returned; in the latter, background: properties will be added.)

image()

image() is a proxy for slice. Optionally, we could remove processing of the rectangle for image. image() should be used when referencing, for instance, an icon; it won't be sliced out of a larger image, and as such, slice() does not make much sense.

slices()

slices() will act like (or be) a SCSS mixin, and take two arguments:

@include slices(filename[, options]);

It is a mixin because it adds additional properties and rules, rather than acting like a function.

slices() will generate all non-0-sized slices out of the following:

  • top
  • left
  • right
  • bottom
  • top-left
  • top-right
  • bottom-left
  • bottom-right

It will generate them nested under the current selector, and give them the class names specified above.

Options:

  • left, top, width, height, right, bottom: define rectangle of the "middle" slice. The other slices will be to the top, bottom, left, right, etc.

  • *-offset: for any given slice, the offset (as described for slice())

  • *-padding: for any given slice, the padding (as described for slice())

  • *-anchor: for any given slice, the anchor

  • skip: a list of slices, separated by spaces, to skip including in the output. This can be useful if the image being sliced only defines a new left slice or a new right slice, for instance.

  • middle: by default, only the first column of the middle is included. The value for "middle" is a set of two values, each of which is either a number or the key 'whole'. For example, the default is:

    $middle: 1 fill
    

@theme and 'theme'

The @theme directive is a little questionable. It changes the scope of the 'theme' magic keyword, which may be referenced in the CSS selectors. This allows you to easily scope selectors to the theme you are in:

@theme(ace.my-app) {
  .msie theme.button { }
}

Gets replaced with:

.msie .ace.my-app.button { }

While it is definitely not desireable to be typing in .ace.my-app all the time, there may be a better solution.

Standard SCSS nesting can handle it too, but is a little less obvious:

.ace.my-app {
  .msie &.button { }
}

With the "theme" magic keyword (or some other keyword), however, we'd also potentially be able to scope the theme namespaces automatically so you don't have to use @theme for anything but subthemes (Chance 1.0 did this: a command-line argument allowed you to specify the theme name).

DISCUSSION POINT: Should @theme be supported? What should the magic keyword be?

Chance Output

Chance will generate output containing a CSS file and, potentially, some image files ("instances" covers how more than one version of CSS can be created).

Charnce's output CSS, if using data: urls, will only contain each slice once. It accomplishes this by using SCSS's @extend feature; in short, commands like slices() get replaced with code like this:

@extend .__resource_button_0_0_3_24;

SCSS then extends the original .__resource_button_0_0_3_24 rule to include the current selector. This way, the data: url only gets included once, but can be used in multiple other places.

NOTE: the name of the slices is up to Chance implementation except that the slice names must include the file name minus extension and path. This means that Chance could use a numerical identifier in addition to prevent conflicts, like so:

.__resource1523_button_0_0_3_24

Chance Interface

WARNING: IMPLEMENTATION DETAILS AHEAD!

Chance will have a simple API that many different front-ends could be made for: the Ruby build tools, an HTTP service, a helper process for JS build tools, etc.

Chance is designed so that any proxy layer would not need to be overly complex: there is only one exposed Class (as objects are not so easy to reference over-the-wire).

The one "Class" is Chance, which is a Chance instance. You would have a Chance instance for every .lproj folder, resources folder, etc.

Chance (not instance) supports the following methods:

  • addFile(identifier, [contents])
  • removeFile(identifier, [contents])
  • updateFile(identifier, [contents])

These add files to the "world" of Chance.

Each of the *File() commands takes an identifier and, optionally, the content. If the content is not specified, the identifier is assumed to be a path, and is used.

Some systems may already have preprocessed content to send; others may just want to point Chance at the files to use.

Chance Instances

Each Chance instance has the following properties:

  • options
  • urlPrefix
  • cssFiles
  • resources
  • paths

The urlPrefix is the location of Chance's output within the package. In current SC, this would likely just be "", because Chance's output is directly in the package folder.

The cssFiles property holds a list of CSS files, including at minimum one main CSS file, and potentially a data-url file. resources is a set of all other resources used or generated, including any sprited images.

Each "file" is a hash:

  • path: the path to the file. style.css, images.css, repeat-x.png, etc.; the CSS will reference the images with static_url(path), and as such, any paths must be relative to the package/framework.

    The path will include urlPrefix.

  • content: The actual contents of the file.

  • contentType: the file's content type (image/png, text/css, etc.)

The "paths" property is a set of paths for Chance to search for any additional CSS included by any @import statements. As this forces the file to be processed without Chance pre-processing (as Chance doesn't know the file), this is not recommended except for non-Chance code.

Each Chance Instance supports the following methods:

  • mapFile(path, identifier)
  • unmapFile(path, identifier)
  • update()
  • clear()

The paths given to includeFile and excludeFile must already be in Chance's "world," added via Chance.addFile, Chance.removeFile, and Chance.updateFile.

The 'path' is the path within this Chance instance.

NOTE: while slightly cumbersome, the files are kept global because otherwise the same file would end up in memory multiple times: for instance, if "icon.png" was in resources/, but Chance instances for ten .lproj folders were to include this file, we do not want ten copies of the image in memory.

Updating is always manual. If you make any changes, call update() to see the result. update() is synchronous for simplicity. The best way to achieve better performance is to run multiple simultaneous Chance instances and call all methods through a communication layer (the JS tools will do this, creating 1 Chance instance per package (single instance shared between all localizations).

options

The 'options' property is a hash of settings. Any setting can go here and will be usable by SCSS; a few settings have special meaning to Chance:

  • compress: if true, the output will be minified (allowing us to get rid of YUI minifier).
  • shouldUseDataURLs: if true, any slices(), etc. will use data urls.

Other settings we might add (but Chance won't, itself, know):

  • msie: if YES, this Chance instance is generating output for IE, and as such, IE-specific code must be included.

The mode is not allowed to change after a Chance instance is created (the Chance instance must be recreated).

sc_require()

Chance will combine all CSS into a giant string before processing with SCSS. To ensure proper ordering, Chance will understand sc_require().

Build Tool Usage

The build tools will create at least two instances of Chance for each localized theme: one for data urls and non-data urls.

If in production mode, the build tools will set compress to YES.

A Chance instance will be created for each resources or .lproj folder.

The build tools must also have a way of producing multiple JavaScript files.

Currently, the ruby tools generate:

  • javascript.js
  • javascript-packed.js
  • style.css
  • index.html
  • resources/

Instead, they should produce:

  • javascript-d.js
  • javascript-packed-d.js
  • javascript.js
  • javascript-packed.js
  • resources/

The JavaScript files should all call:

SC.loadCSS(name, cssText);

Name will be inserted as a custom attribute in the injected <style> tags so that developers can identify which files are which. The names consist of the package/framework name followed by the CSS file name (for instance, style.css or images.css).

The SC.loadCSS commands will be injected into the JavaScript files.

Manifest

The current HTML5 manifests often contains dozens of files. With these changes, it should contain roughly two:

  • index.html
  • javascript-d.js

This could make things dramatically faster.

SC Bootstrap

SC's bootstrap code will be modified to decide which JavaScript file to load. A good way to do this:

  • in head, calculate SC.browser like we currently do

  • in head, have Loading CSS (but no other CSS), all inline

  • in head, decide what JavaScript file to load

  • in body, have Loading HTML

  • in body, a <script> tag will contain something like:

    var script = document.createElement("SCRIPT"); script.setAttribute('href', theCalculatedURL); script.setAttribute('type', "text/javascript"); document.body.appendChild(script);

Suggested Build Tool Extensions

  • sc-gen: generate a theme.js for the app by default (or define the theme in core.js).

  • sc-gen: generate boilerplate CSS:

       /* put global styles that affect <body>, etc. out here */
      .sc-ace.my-app {
        /* Put CSS to style your app controls here, prefixed with & 
           Example: (will turn all buttons in your app blue)
            
           &.button {
             background: blue;
           }
         */
      }
    
      /* or */
      @theme(sc-ace.my-app) {
        /* Use theme.itemName to theme any control. For instance,
           theme.button may be used to theme a button.
        */
      }
      
      /* or, if we already know theme name, base theme name, etc. */
      
      /* Use theme.itemName to theme any control. For instance,
         theme.button { background: blue } may be used to theme a button.
       */
    

Suggested SproutCore Framework Changes or Additions

To complement these changes, a few tweaks could be made to SproutCore:

  • RenderContext.render(renderer, args...)
  • SlicesRenderer
    • would automatically generate elements for 3 or 9 slices (as asked for), with the slice class names Chance uses.

With these, it would become much simpler to write the JavaScript portion of the themes and link them to the CSS.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment