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 sc_require('css/css_rule') ;
  9 
 10 /**
 11   @class SC.CSSStyleSheet
 12 
 13   A style sheet object wraps a document style sheet object. `C.CSSStyleSheet`
 14   will re-use stylesheet objects as needed.
 15 
 16   @extends SC.Object
 17 */
 18 SC.CSSStyleSheet = SC.Object.extend(
 19 /** @scope SC.CSSStyleSheet.prototype */ {
 20 
 21   init: function() {
 22     sc_super() ;
 23 
 24     var ss = this.styleSheet ;
 25     if (!ss) {
 26       // create the stylesheet object the hard way (works everywhere)
 27       ss = this.styleSheet = document.createElement('style') ;
 28       ss.type = 'text/css' ;
 29       var head = document.getElementsByTagName('head')[0] ;
 30       if (!head) head = document.documentElement ; // fix for Opera
 31       head.appendChild(ss) ;
 32     }
 33 
 34     // cache this object for later
 35     var ssObjects = this.constructor.styleSheets ;
 36     if (!ssObjects) ssObjects = this.constructor.styleSheets = {} ;
 37     ssObjects[SC.guidFor(ss)] ;
 38 
 39     // create rules array
 40     var rules = ss.rules || SC.EMPTY_ARRAY ;
 41     var array = SC.SparseArray.create(rules.length) ;
 42     array.delegate = this ;
 43     this.rules = array ;
 44 
 45     return this ;
 46   },
 47 
 48   /**
 49     @type Boolean YES if the stylesheet is enabled.
 50   */
 51   isEnabled: function(key, val) {
 52     if (val !== undefined) {
 53       this.styleSheet.disabled = !val ;
 54     }
 55     return !this.styleSheet.disabled ;
 56   }.property(),
 57   isEnabledBindingDefault: SC.Binding.bool(),
 58 
 59   /**
 60     **DO NOT MODIFY THIS OBJECT DIRECTLY!!!!** Use the methods defined on this
 61     object to update properties of the style sheet; otherwise, your changes
 62     will not be reflected.
 63 
 64     @type CSSStyleSheet RO
 65   */
 66   styleSheet: null,
 67 
 68   /**
 69     @type String
 70   */
 71   href: function(key, val) {
 72     if (val !== undefined) {
 73       this.styleSheet.href = val ;
 74     }
 75     else return this.styleSheet.href ;
 76   }.property(),
 77 
 78   /**
 79     @type String
 80   */
 81   title: function(key, val) {
 82     if (val !== undefined) {
 83       this.styleSheet.title = val ;
 84     }
 85     else return this.styleSheet.title ;
 86   }.property(),
 87 
 88   /**
 89     @type SC.Array contains SC.CSSRule objects
 90   */
 91   rules: null,
 92 
 93   /**
 94     You can also insert and remove rules on the rules property array.
 95   */
 96   insertRule: function(rule) {
 97     var rules = this.get('rules') ;
 98   },
 99 
100   /**
101     You can also insert and remove rules on the rules property array.
102   */
103   deleteRule: function(rule) {
104     var rules = this.get('rules') ;
105     rules.removeObject(rule) ;
106   },
107 
108   // TODO: implement a destroy method
109 
110   /**
111     @private
112 
113     Invoked by the sparse array whenever it needs a particular index
114     provided.  Provide the content for the index.
115   */
116   sparseArrayDidRequestIndex: function(array, idx) {
117     // sc_assert(this.rules === array) ;
118     var rules = this.styleSheet.rules || SC.EMPTY_ARRAY ;
119     var rule = rules[idx] ;
120     if (rule) {
121       array.provideContentAtIndex(idx, SC.CSSRule.create({
122         rule: rule,
123         styleSheet: this
124       }));
125     }
126   },
127 
128   /** @private synchronize the browser's rules array with our own */
129   sparseArrayDidReplace: function(array, idx, amt, objects) {
130     var cssRules = objects.collect(function(obj) { return obj.rule; }) ;
131     this.styleSheet.rules.replace(idx, amt, cssRules) ;
132   }
133 
134 });
135 
136 SC.mixin(SC.CSSStyleSheet,
137 /** SC.CSSStyleSheet */{
138 
139   /**
140     Find a stylesheet object by name or href. If by name, `.css` will be
141     appended automatically.
142 
143         var ss = SC.CSSStyleSheet.find('style.css') ;
144         var ss2 = SC.CSSStyleSheet.find('style') ; // same thing
145         sc_assert(ss === ss2) ; // SC.CSSStyleSheet objects are stable
146 
147     @param {String} nameOrUrl a stylesheet name or href to find
148     @returns {SC.CSSStyleSheet} null if not found
149   */
150   find: function(nameOrUrl) {
151     var isUrl = nameOrUrl ? nameOrUrl.indexOf('/') >= 0 : NO ;
152 
153     if (!nameOrUrl) return null ; // no name or url? fail!
154 
155     if (!isUrl && nameOrUrl.indexOf('.css') == -1) {
156       nameOrUrl = nameOrUrl + '.css' ;
157     }
158 
159     // initialize styleSheet cache
160     var ssObjects = this.styleSheets ;
161     if (!ssObjects) ssObjects = this.styleSheets = {} ;
162 
163     var styleSheets = document.styleSheets ;
164     var ss, ssName, ssObject, guid ;
165     for (var idx=0, len=styleSheets.length; idx < len; ++idx) {
166       ss = styleSheets[idx] ;
167       if (isUrl) {
168         if (ss.href === nameOrUrl) {
169           guid = SC.guidFor(ss) ;
170           ssObject = ssObjects[guid] ;
171           if (!ssObject) {
172             // cache for later
173             ssObject = ssObjects[guid] = this.create({ styleSheet: ss }) ;
174           }
175           return ssObject ;
176         }
177       }
178       else {
179         if (ssName = ss.href) {
180           ssName = ssName.split('/') ; // break up URL
181           ssName = ssName[ssName.length-1] ; // get last component
182           if (ssName == nameOrUrl) {
183             guid = SC.guidFor(ss) ;
184             ssObject = ssObjects[guid] ;
185             if (!ssObject) {
186               // cache for later
187               ssObject = ssObjects[guid] = this.create({ styleSheet: ss }) ;
188             }
189             return ssObject ;
190           }
191         }
192       }
193     }
194     return null ; // stylesheet not found
195   },
196 
197   styleSheets: null
198 
199 });
200