Skip to content

Commit b73c4eb

Browse files
committed
ie) resolve recommendations
1 parent 91e4ff6 commit b73c4eb

File tree

7 files changed

+76
-85
lines changed

7 files changed

+76
-85
lines changed

packages/binding.core/spec/textInputBehaviors.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,35 @@ describe('Binding: TextInput', function () {
2929
bindingHandlers.set(coreBindings)
3030
})
3131

32+
it('User-Agent detection IE10 + IE11', function () {
33+
let uaList = [
34+
{ agent: 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0', version: 9 },
35+
{ agent: 'Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)', version: 10 },
36+
{ agent: 'Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko', version: 11 },
37+
{
38+
agent:
39+
'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/39.0.2171.71 Safari/537.36 Edge/12.0',
40+
version: null
41+
},
42+
{
43+
agent:
44+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Safari/537.36 Edge/13.10586',
45+
version: null
46+
},
47+
{
48+
agent:
49+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 15_7_3) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/26.0 Safari/605.1.15',
50+
version: null
51+
}
52+
]
53+
54+
uaList.forEach(userAgent => {
55+
let match = userAgent['agent'].match(/MSIE ([^ ;]+)|rv:([^ )]+)/)
56+
let ieVersion = match && (parseFloat(match[1]) || parseFloat(match[2]))
57+
expect(ieVersion).toBe(userAgent['version'])
58+
})
59+
})
60+
3261
it('Should assign the value to the node', function () {
3362
testNode.innerHTML = "<input data-bind='textInput:123' />"
3463
applyBindings(null, testNode)

packages/binding.core/spec/valueBehaviors.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -310,7 +310,7 @@ describe('Binding: Value', function () {
310310
// 2. Modify the textbox further
311311
// 3. Tab out of the textbox
312312
testNode.childNodes[0].value = 'some user-entered value'
313-
triggerEvent(testNode.childNodes[0], 'propertychange')
313+
//IE: triggerEvent(testNode.childNodes[0], 'propertychange')
314314
triggerEvent(testNode.childNodes[0], 'change')
315315
expect(myObservable()).toEqual('some user-entered value')
316316
expect(numUpdates).toEqual(1)
@@ -322,7 +322,7 @@ describe('Binding: Value', function () {
322322
// 2. Tab out of the textbox
323323
// 3. Reselect, edit, then tab out of the textbox
324324
testNode.childNodes[0].value = 'different user-entered value'
325-
triggerEvent(testNode.childNodes[0], 'propertychange')
325+
//IE: triggerEvent(testNode.childNodes[0], 'propertychange')
326326
triggerEvent(testNode.childNodes[0], 'blur')
327327
expect(myObservable()).toEqual('some user-entered value')
328328
expect(numUpdates).toEqual(1)

packages/binding.core/src/textInput.ts

Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { unwrap } from '@tko/observable'
44

55
import { BindingHandler } from '@tko/bind'
66

7-
let operaVersion, safariVersion, firefoxVersion
7+
let operaVersion, safariVersion, firefoxVersion, ieVersion
88

99
/**
1010
* TextInput binding handler for modern browsers (legacy below).
@@ -100,6 +100,13 @@ class TextInput extends BindingHandler {
100100
}
101101
}
102102

103+
class TextInputIE extends TextInput {
104+
override eventsIndicatingSyncValueChange() {
105+
//keypress: All versions (including 11) of Internet Explorer have a bug that they don't generate an input or propertychange event when ESC is pressed
106+
return [...super.eventsIndicatingSyncValueChange(), 'keypress']
107+
}
108+
}
109+
103110
// Safari <5 doesn't fire the 'input' event for <textarea> elements (it does fire 'textInput'
104111
// but only when typing). So we'll just catch as much as we can with keydown, cut, and paste.
105112
class TextInputLegacySafari extends TextInput {
@@ -134,17 +141,23 @@ if (w.navigator) {
134141
const parseVersion = matches => matches && parseFloat(matches[1])
135142
const userAgent = w.navigator.userAgent
136143
const isChrome = userAgent.match(/Chrome\/([^ ]+)/)
137-
// Detect various browser versions because some old versions don't fully support the 'input' event
138-
operaVersion = w.opera && w.opera.version && parseInt(w.opera.version())
139-
safariVersion = parseVersion(userAgent.match(/Version\/([^ ]+) Safari/))
140-
firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]*)/))
144+
if (!isChrome) {
145+
// Detect various browser versions because some old versions don't fully support the 'input' event
146+
operaVersion = w.opera && w.opera.version && parseInt(w.opera.version())
147+
safariVersion = parseVersion(userAgent.match(/Version\/([^ ]+) Safari/))
148+
firefoxVersion = parseVersion(userAgent.match(/Firefox\/([^ ]*)/))
149+
const ieMatch = userAgent.match(/MSIE ([^ ;]+)|rv:([^ )]+)/)
150+
ieVersion = ieMatch && (parseFloat(ieMatch[1]) || parseFloat(ieMatch[2]))
151+
}
141152
}
142153

143154
export const textInput =
144-
safariVersion && safariVersion < 5
145-
? TextInputLegacySafari
146-
: operaVersion < 11
147-
? TextInputLegacyOpera
148-
: firefoxVersion && firefoxVersion < 4
149-
? TextInputLegacyFirefox
150-
: TextInput
155+
ieVersion && ieVersion <= 11
156+
? TextInputIE
157+
: safariVersion && safariVersion < 5
158+
? TextInputLegacySafari
159+
: operaVersion && operaVersion < 11
160+
? TextInputLegacyOpera
161+
: firefoxVersion && firefoxVersion < 4
162+
? TextInputLegacyFirefox
163+
: TextInput

packages/binding.core/src/value.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class value extends BindingHandler {
6767
&& this.$element.type == 'text'
6868
&& this.$element.autocomplete != 'off'
6969
&& (!this.$element.form || this.$element.form.autocomplete != 'off')
70-
&& (window.navigator.userAgent.match(/MSIE ([^ ]+)/) || window.navigator.userAgent.match(/rv:([^ )]+)/)) //only if IE10 or IE11
70+
&& window.navigator.userAgent.match(/MSIE ([^ ;]+)|rv:([^ )]+)/) //only if IE10 or IE11
7171
)
7272
}
7373

packages/utils/src/dom/data.ts

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -18,25 +18,21 @@ function isSafeKey(key: string): boolean {
1818
* prevents using it cross-window, so instead we just store the data directly
1919
* on the node. See https://github.com/knockout/knockout/issues/2141
2020
*/
21-
const modern = {
22-
getDataForNode(node: Node, createIfNotFound: boolean) {
23-
let dataForNode = node[dataStoreSymbol]
24-
if (!dataForNode && createIfNotFound) {
25-
dataForNode = node[dataStoreSymbol] = {}
26-
}
27-
return dataForNode
28-
},
29-
30-
clear(node: Node) {
31-
if (node[dataStoreSymbol]) {
32-
delete node[dataStoreSymbol]
33-
return true
34-
}
35-
return false
21+
function getDataForNode(node: Node, createIfNotFound: boolean): any {
22+
let dataForNode = node[dataStoreSymbol]
23+
if (!dataForNode && createIfNotFound) {
24+
dataForNode = node[dataStoreSymbol] = {}
3625
}
26+
return dataForNode
3727
}
3828

39-
const { getDataForNode, clear } = modern
29+
function clear(node: Node): boolean {
30+
if (node[dataStoreSymbol]) {
31+
delete node[dataStoreSymbol]
32+
return true
33+
}
34+
return false
35+
}
4036

4137
/**
4238
* Create a unique key-string identifier.

packages/utils/src/dom/event.ts

Lines changed: 2 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,15 @@
44

55
import { objectForEach } from '../object'
66
import { catchFunctionErrors } from '../error'
7-
87
import { tagNameLower } from './info'
9-
import { addDisposeCallback } from './disposal'
8+
109
import options from '../options'
1110

1211
// Represent the known event types in a compact way, then at runtime transform it into a hash with event name as key (for fast lookup)
1312
const knownEvents = {},
1413
knownEventTypesByEventName = {}
1514

16-
const keyEventTypeName =
17-
options.global.navigator && /Firefox\/2/i.test(options.global.navigator.userAgent) ? 'KeyboardEvent' : 'UIEvents'
18-
19-
knownEvents[keyEventTypeName] = ['keyup', 'keydown', 'keypress']
15+
knownEvents['UIEvents'] = ['keyup', 'keydown', 'keypress']
2016

2117
knownEvents['MouseEvents'] = [
2218
'click',
@@ -45,14 +41,6 @@ function isClickOnCheckableElement(element: Element, eventType: string) {
4541
return inputType == 'checkbox' || inputType == 'radio'
4642
}
4743

48-
function hasIEAttachEvents(
49-
el: Element
50-
): el is Element & { attachEvent: (event: string, handler: EventListener) => void } & {
51-
detachEvent: (event: string, handler: EventListener) => void
52-
} {
53-
return typeof (el as any).attachEvent === 'function' && typeof (el as any).detachEvent === 'function'
54-
}
55-
5644
export function registerEventHandler(
5745
element: Element,
5846
eventType: string,
@@ -67,18 +55,6 @@ export function registerEventHandler(
6755
jQuery(element).on(eventType, wrappedHandler)
6856
} else if (typeof element.addEventListener === 'function') {
6957
element.addEventListener(eventType, wrappedHandler, eventOptions)
70-
} else if (hasIEAttachEvents(element)) {
71-
const attachEventHandler = function (event) {
72-
wrappedHandler.call(element, event)
73-
}
74-
const attachEventName = 'on' + eventType
75-
element.attachEvent(attachEventName, attachEventHandler)
76-
77-
// IE does not dispose attachEvent handlers automatically (unlike with addEventListener)
78-
// so to avoid leaks, we have to remove them manually. See bug #856
79-
addDisposeCallback(element, function () {
80-
element.detachEvent(attachEventName, attachEventHandler)
81-
})
8258
} else {
8359
throw new Error("Browser doesn't support addEventListener or attachEvent")
8460
}
@@ -88,19 +64,13 @@ function hasClick(element: Element): element is Element & { click(): void } {
8864
return typeof (element as any).click === 'function'
8965
}
9066

91-
function hasFireEvent(element: Element): element is Element & { fireEvent(eventType: string): void } {
92-
return typeof (element as any).click === 'function'
93-
}
94-
9567
export function triggerEvent(element: Element, eventType: string): void {
9668
if (!(element && element.nodeType)) {
9769
throw new Error('element must be a DOM node when calling triggerEvent')
9870
}
9971

10072
// For click events on checkboxes and radio buttons, jQuery toggles the element checked state *after* the
10173
// event handler runs instead of *before*. (This was fixed in 1.9 for checkboxes but not for radio buttons.)
102-
// IE doesn't change the checked state when you trigger the click event using "fireEvent".
103-
// In both cases, we'll use the click method instead.
10474
const useClickWorkaround = isClickOnCheckableElement(element, eventType)
10575

10676
if (!options.useOnlyNativeEvents && options.jQuery && !useClickWorkaround) {
@@ -132,8 +102,6 @@ export function triggerEvent(element: Element, eventType: string): void {
132102
}
133103
} else if (useClickWorkaround && hasClick(element)) {
134104
element.click()
135-
} else if (hasFireEvent(element)) {
136-
element.fireEvent('on' + eventType)
137105
} else {
138106
throw new Error("Browser doesn't support triggering events")
139107
}

packages/utils/src/dom/virtualElements.ts

Lines changed: 5 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,35 +11,22 @@
1111
// The point of all this is to support containerless templates (e.g., <!-- ko foreach:someCollection -->blah<!-- /ko -->)
1212
// without having to scatter special cases all over the binding and templating code.
1313

14-
// IE 9 cannot reliably read the "nodeValue" property of a comment node (see https://github.com/SteveSanderson/knockout/issues/186)
15-
// but it does give them a nonstandard alternative property called "text" that it can read reliably. Other browsers don't have that property.
16-
// So, use node.text where available, and node.nodeValue elsewhere
1714
import { emptyDomNode, setDomNodeChildren as setRegularDomNodeChildren } from './manipulation'
1815
import { removeNode } from './disposal'
1916
import { tagNameLower } from './info'
2017
import * as domData from './data'
2118
import options from '../options'
2219

23-
const commentNodesHaveTextProperty = options.document && 'text' in options.document.createComment('test') //in case of IE8..
24-
25-
export const startCommentRegex = commentNodesHaveTextProperty
26-
? /^<!--\s*ko(?:\s+([\s\S]+))?\s*-->$/
27-
: /^\s*ko(?:\s+([\s\S]+))?\s*$/
28-
export const endCommentRegex = commentNodesHaveTextProperty ? /^<!--\s*\/ko\s*-->$/ : /^\s*\/ko\s*$/
20+
export const startCommentRegex = /^\s*ko(?:\s+([\s\S]+))?\s*$/
21+
export const endCommentRegex = /^\s*\/ko\s*$/
2922
const htmlTagsWithOptionallyClosingChildren = { ul: true, ol: true }
3023

3124
export function isStartComment(node) {
32-
return (
33-
node.nodeType === Node.COMMENT_NODE
34-
&& startCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue)
35-
)
25+
return node.nodeType === Node.COMMENT_NODE && startCommentRegex.test(node.nodeValue)
3626
}
3727

3828
export function isEndComment(node) {
39-
return (
40-
node.nodeType === Node.COMMENT_NODE
41-
&& endCommentRegex.test(commentNodesHaveTextProperty ? node.text : node.nodeValue)
42-
)
29+
return node.nodeType === Node.COMMENT_NODE && endCommentRegex.test(node.nodeValue)
4330
}
4431

4532
function isUnmatchedEndComment(node) {
@@ -248,9 +235,7 @@ export function previousSibling(node) {
248235
}
249236

250237
export function virtualNodeBindingValue(node): string | null {
251-
const regexMatch = (commentNodesHaveTextProperty ? node.text : node.nodeValue).match(
252-
startCommentRegex
253-
) as RegExpMatchArray
238+
const regexMatch = node.nodeValue.match(startCommentRegex) as RegExpMatchArray
254239
return regexMatch ? regexMatch[1] : null
255240
}
256241

0 commit comments

Comments
 (0)