Bläddra i källkod

IE9 compatibility

- fixed: Array method wrapping
- fixed: console
- fixed: classList polyfiil
- fixed: select options
Evan You 12 år sedan
förälder
incheckning
7b17f80eee

+ 0 - 1
TODO.md

@@ -1,4 +1,3 @@
-- add escape: {{{ things in here should not be parsed }}}
 - sd-transition
 - component examples
 - tests

+ 2 - 2
examples/todomvc/index.html

@@ -29,7 +29,7 @@
                         class="todo"
                         sd-repeat="todo:todos"
                         sd-show="todoFilter(todo)"
-                        sd-class="completed:todo.completed, editing:editedTodo===todo"
+                        sd-class="completed:todo.completed, editing:todo == editedTodo"
                     >
                         <div class="view">
                             <input
@@ -44,9 +44,9 @@
                         <input
                             class="edit"
                             type="text"
-                            sd-focus="editedTodo===todo"
                             sd-on="blur:doneEdit, keyup:doneEdit | key enter, keyup:cancelEdit | key esc"
                             sd-model="todo.title"
+                            sd-todo-focus="todo == editedTodo"
                         >
                     </li>
                 </ul>

+ 7 - 0
examples/todomvc/js/app.js

@@ -4,6 +4,13 @@ var filters = {
     completed: function (todo) { return todo.completed }
 }
 
+Seed.directive('todo-focus', function (value) {
+    var el = this.el
+    if (value) {
+        setTimeout(function () { el.focus() }, 0)
+    }
+})
+
 var app = new Seed({
 
     el: '#todoapp',

+ 6 - 5
src/compiler.js

@@ -8,6 +8,7 @@ var Emitter     = require('./emitter'),
     DepsParser  = require('./deps-parser'),
     ExpParser   = require('./exp-parser'),
     slice       = Array.prototype.slice,
+    log         = utils.log,
     vmAttr,
     repeatAttr,
     partialAttr,
@@ -30,7 +31,7 @@ function Compiler (vm, options) {
 
     // initialize element
     compiler.setupElement(options)
-    utils.log('\nnew VM instance: ', compiler.el, '\n')
+    log('\nnew VM instance:', compiler.el.tagName, '\n')
 
     // copy scope properties to vm
     var scope = options.scope
@@ -365,7 +366,7 @@ CompilerProto.createBinding = function (key, isExp) {
         // we need to generate an anonymous computed property for it
         var result = ExpParser.parse(key)
         if (result) {
-            utils.log('  created anonymous binding: ' + key)
+            log('  created anonymous binding: ' + key)
             binding.value = { get: result.getter }
             compiler.markComputed(binding)
             compiler.exps.push(binding)
@@ -382,7 +383,7 @@ CompilerProto.createBinding = function (key, isExp) {
             utils.warn('  invalid expression: ' + key)
         }
     } else {
-        utils.log('  created binding: ' + key)
+        log('  created binding: ' + key)
         bindings[key] = binding
         // make sure the key exists in the object so it can be observed
         // by the Observer!
@@ -427,7 +428,7 @@ CompilerProto.ensurePath = function (key) {
  */
 CompilerProto.define = function (key, binding) {
 
-    utils.log('    defined root binding: ' + key)
+    log('    defined root binding: ' + key)
 
     var compiler = this,
         vm = compiler.vm,
@@ -536,7 +537,7 @@ CompilerProto.getOption = function (type, id) {
  */
 CompilerProto.destroy = function () {
     var compiler = this
-    utils.log('compiler destroyed: ', compiler.vm.$el)
+    log('compiler destroyed: ', compiler.vm.$el)
     // unwatch
     compiler.observer.off()
     compiler.emitter.off()

+ 19 - 11
src/directives/index.js

@@ -31,15 +31,6 @@ module.exports = {
     visible: function (value) {
         this.el.style.visibility = value ? '' : 'hidden'
     },
-    
-    focus: function (value) {
-        var el = this.el
-        if (value) {
-            setTimeout(function () {
-                el.focus()
-            }, 0)
-        }
-    },
 
     class: function (value) {
         if (this.arg) {
@@ -70,14 +61,31 @@ module.exports = {
                 ? 'checked'
                 : 'value'
             self.set = function () {
+                self.lock = true
                 self.vm.$set(self.key, el[self.attr])
+                self.lock = false
             }
             el.addEventListener(self.event, self.set)
         },
         update: function (value) {
-            if (this.el.type === 'radio') {
+            var self = this,
+                el   = self.el
+            if (self.lock) return
+            if (el.type === 'radio') {
                 /* jshint eqeqeq: false */
-                this.el.checked = value == this.el.value
+                el.checked = value == el.value
+            } else if (el.tagName === 'SELECT') {
+                // you cannot set <select>'s value in stupid IE9
+                var o = el.options,
+                    i = o.length,
+                    index = -1
+                while (i--) {
+                    if (o[i].value == value) {
+                        index = i
+                        break
+                    }
+                }
+                o.selectedIndex = index
             } else {
                 this.el[this.attr] = this.attr === 'checked'
                     ? !!value

+ 17 - 10
src/observer.js

@@ -1,9 +1,10 @@
-var Emitter = require('./emitter'),
-    utils   = require('./utils'),
-    typeOf  = utils.typeOf,
-    def     = utils.defProtected,
-    slice   = Array.prototype.slice,
-    methods = ['push','pop','shift','unshift','splice','sort','reverse']
+var Emitter  = require('./emitter'),
+    utils    = require('./utils'),
+    typeOf   = utils.typeOf,
+    def      = utils.defProtected,
+    slice    = Array.prototype.slice,
+    methods  = ['push','pop','shift','unshift','splice','sort','reverse'],
+    hasProto = ({}).__proto__ // fix for IE9
 
 // The proxy prototype to replace the __proto__ of
 // an observed array
@@ -11,7 +12,7 @@ var ArrayProxy = Object.create(Array.prototype)
 
 // Define mutation interceptors so we can emit the mutation info
 methods.forEach(function (method) {
-    utils.defProtected(ArrayProxy, method, function () {
+    def(ArrayProxy, method, function () {
         var result = Array.prototype[method].apply(this, arguments)
         this.__observer__.emit('mutate', this.__observer__.path, this, {
             method: method,
@@ -19,7 +20,7 @@ methods.forEach(function (method) {
             result: result
         })
         return result
-    })
+    }, !hasProto)
 })
 
 // Augment it with several convenience methods
@@ -42,7 +43,7 @@ var extensions = {
 }
 
 for (var method in extensions) {
-    utils.defProtected(ArrayProxy, method, extensions[method])
+    def(ArrayProxy, method, extensions[method], !hasProto)
 }
 
 /*
@@ -74,7 +75,13 @@ function watchArray (arr, path, observer) {
     def(arr, '__observer__', observer)
     observer.path = path
     /* jshint proto:true */
-    arr.__proto__ = ArrayProxy
+    if (hasProto) {
+        arr.__proto__ = ArrayProxy
+    } else {
+        for (var key in ArrayProxy) {
+            def(arr, key, ArrayProxy[key])
+        }
+    }
 }
 
 /*

+ 11 - 7
src/utils.js

@@ -1,5 +1,7 @@
 var config    = require('./config'),
-    toString  = Object.prototype.toString
+    toString  = Object.prototype.toString,
+    join      = Array.prototype.join,
+    console   = window.console
 
 module.exports = {
 
@@ -14,10 +16,10 @@ module.exports = {
      *  This avoids it being included in JSON.stringify
      *  or for...in loops.
      */
-    defProtected: function (obj, key, val) {
+    defProtected: function (obj, key, val, enumerable) {
         if (obj.hasOwnProperty(key)) return
         Object.defineProperty(obj, key, {
-            enumerable: false,
+            enumerable: !!enumerable,
             configurable: false,
             value: val
         })
@@ -43,15 +45,17 @@ module.exports = {
      *  log for debugging
      */
     log: function () {
-        if (config.debug) console.log.apply(console, arguments)
-        return this
+        if (config.debug && console) {
+            console.log(join.call(arguments, ' '))
+        }
     },
     
     /*
      *  warn for debugging
      */
     warn: function() {
-        if (config.debug) console.warn.apply(console, arguments)
-        return this
+        if (config.debug && console) {
+            console.warn(join.call(arguments, ' '))
+        }
     }
 }

+ 93 - 0
test/classList.js

@@ -0,0 +1,93 @@
+(function () {
+
+if (typeof window.Element === "undefined" || "classList" in document.documentElement) return;
+
+// adds indexOf to Array prototype for IE support
+if (!Array.prototype.indexOf) {
+    Array.prototype.indexOf = function(obj, start) {
+        for (var i = (start || 0), j = this.length; i < j; i++) {
+            if (this[i] === obj) { return i; }
+        }
+        return -1;
+    }
+}
+
+var prototype = Array.prototype,
+    indexOf = prototype.indexOf,
+    slice = prototype.slice,
+    push = prototype.push,
+    splice = prototype.splice,
+    join = prototype.join;
+
+function DOMTokenList(el) {  
+  this._element = el;
+  if (el.className != this._classCache) {
+    this._classCache = el.className;
+
+    if (!this._classCache) return;
+    
+      // The className needs to be trimmed and split on whitespace
+      // to retrieve a list of classes.
+      var classes = this._classCache.replace(/^\s+|\s+$/g,'').split(/\s+/),
+        i;
+    for (i = 0; i < classes.length; i++) {
+      push.call(this, classes[i]);
+    }
+  }
+};
+
+function setToClassName(el, classes) {
+  el.className = classes.join(' ');
+}
+
+DOMTokenList.prototype = {
+  add: function(token) {
+    if(this.contains(token)) return;
+    push.call(this, token);
+    setToClassName(this._element, slice.call(this, 0));
+  },
+  contains: function(token) {
+    return indexOf.call(this, token) !== -1;
+  },
+  item: function(index) {
+    return this[index] || null;
+  },
+  remove: function(token) {
+    var i = indexOf.call(this, token);
+     if (i === -1) {
+       return;
+     }
+    splice.call(this, i, 1);
+    setToClassName(this._element, slice.call(this, 0));
+  },
+  toString: function() {
+    return join.call(this, ' ');
+  },
+  toggle: function(token) {
+    if (!this.contains(token)) {
+      this.add(token);
+    } else {
+      this.remove(token);
+    }
+
+    return this.contains(token);
+  }
+};
+
+window.DOMTokenList = DOMTokenList;
+
+function defineElementGetter (obj, prop, getter) {
+    if (Object.defineProperty) {
+        Object.defineProperty(obj, prop,{
+            get : getter
+        })
+    } else {                    
+        obj.__defineGetter__(prop, getter);
+    }
+}
+
+defineElementGetter(Element.prototype, 'classList', function () {
+  return new DOMTokenList(this);            
+});
+
+})();

+ 1 - 0
test/e2e/runner.html

@@ -10,6 +10,7 @@
         <div id="test"></div>
         <script src="../../node_modules/grunt-mocha/node_modules/mocha/mocha.js"></script>
         <script src="../../node_modules/chai/chai.js"></script>
+        <script src="../classList.js"></script>
         <script src="../../dist/seed.js"></script>
         <script>
             mocha.setup('bdd')

+ 1 - 0
test/unit/runner.html

@@ -10,6 +10,7 @@
 		<div id="test" style="display:none"></div>
 		<script src="../../node_modules/grunt-mocha/node_modules/mocha/mocha.js"></script>
 		<script src="../../node_modules/chai/chai.js"></script>
+		<script src="../classList.js"></script>
 		<script src="../seed.test.js"></script>
 		<script>
 			mocha.setup('bdd')

+ 4 - 3
test/unit/specs/api.js

@@ -17,13 +17,14 @@ describe('UNIT: API', function () {
 
         it('should work when changing interpolate tags', function () {
             var testId = 'config-2'
+            // IE treats <% ... %> as a tag... wtf
             Seed.config({
                 interpolateTags: {
-                    open: '<%',
-                    close: '%>'
+                    open: '(%',
+                    close: '%)'
                 }
             })
-            mock(testId, '<% test %>')
+            mock(testId, '(% test %)')
             new Seed({
                 el: '#' + testId,
                 scope: { test: testId }

+ 8 - 26
test/unit/specs/directives.js

@@ -168,29 +168,6 @@ describe('UNIT: Directives', function () {
 
     })
 
-    describe('focus', function () {
-        
-        var dir = mockDirective('focus', 'input')
-
-        it('should focus on the element', function (done) {
-            var focused = false
-            // the el needs to be in the dom and visible to actually
-            // trigger the focus event
-            document.body.appendChild(dir.el)
-            dir.el.addEventListener('focus', function () {
-                focused = true
-            })
-            dir.update(true)
-            // the focus event has a 0ms timeout to make it async
-            setTimeout(function () {
-                assert.ok(focused)
-                document.body.removeChild(dir.el)
-                done()
-            }, 0)
-        })
-
-    })
-
     describe('class', function () {
 
         it('should set class to the value if it has no arg', function () {
@@ -314,8 +291,13 @@ describe('UNIT: Directives', function () {
 
         describe('select', function () {
             
-            var dir = mockDirective('model', 'select')
-            dir.el.innerHTML = '<option>0</option><option>1</option>'
+            var dir = mockDirective('model', 'select'),
+                o1 = document.createElement('option'),
+                o2 = document.createElement('option')
+            o1.value = 0
+            o2.value = 1
+            dir.el.appendChild(o1)
+            dir.el.appendChild(o2)
             dir.bind()
 
             before(function () {
@@ -358,7 +340,7 @@ describe('UNIT: Directives', function () {
         
         describe('input[text] and others', function () {
             
-            var dir = mockDirective('model', 'input', 'email')
+            var dir = mockDirective('model', 'input', 'text')
             dir.bind()
             
             before(function () {