Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
T
todomvc
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Eugene Shen
todomvc
Commits
1d831397
Commit
1d831397
authored
Jul 10, 2012
by
Addy Osmani
Browse files
Options
Browse Files
Download
Plain Diff
Merge pull request #218 from AlbertoMonteiro/master
Knockout - routing with crossroad js
parents
a4b14fa1
43b78f41
Changes
7
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
1187 additions
and
202 deletions
+1187
-202
architecture-examples/knockoutjs/index.html
architecture-examples/knockoutjs/index.html
+67
-50
architecture-examples/knockoutjs/js/app.js
architecture-examples/knockoutjs/js/app.js
+176
-152
architecture-examples/knockoutjs/js/lib/crossroads.js
architecture-examples/knockoutjs/js/lib/crossroads.js
+145
-0
architecture-examples/knockoutjs/js/lib/intro.js
architecture-examples/knockoutjs/js/lib/intro.js
+81
-0
architecture-examples/knockoutjs/js/lib/pattern_lexer.js
architecture-examples/knockoutjs/js/lib/pattern_lexer.js
+211
-0
architecture-examples/knockoutjs/js/lib/route.js
architecture-examples/knockoutjs/js/lib/route.js
+140
-0
architecture-examples/knockoutjs/js/lib/signals.js
architecture-examples/knockoutjs/js/lib/signals.js
+367
-0
No files found.
architecture-examples/knockoutjs/index.html
View file @
1d831397
<!
doctype html>
<!
DOCTYPE html>
<html
lang=
"en"
>
<head>
<head>
<meta
charset=
"utf-8"
>
<meta
http-equiv=
"X-UA-Compatible"
content=
"IE=edge,chrome=1"
>
<meta
content=
"IE=edge,chrome=1"
http-equiv=
"X-UA-Compatible"
>
<title>
Knockout.js • TodoMVC
</title>
<link
rel=
"stylesheet"
href=
"../../assets/base.css"
>
<link
href=
"../../assets/base.css"
rel=
"stylesheet"
>
<!--[if IE]>
<script src="../../assets/ie.js"></script>
<![endif]-->
</head>
<body>
</head>
<body>
<section
id=
"todoapp"
>
<header
id=
"header"
>
<h1>
todos
</h1>
<input
id=
"new-todo"
type=
"text"
data-bind=
"value: current, valueUpdate: 'afterkeydown', enterKey: add"
placeholder=
"What needs to be done?"
autofocus
>
<input
id=
"new-todo"
autofocus
data-bind=
"value: current, valueUpdate: 'afterkeydown', enterKey: add"
placeholder=
"What needs to be done?"
type=
"text"
>
</header>
<section
id=
"main"
data-bind=
"visible: todos().length"
>
<input
id=
"toggle-all"
type=
"checkbox"
data-bind=
"checked: allCompleted"
>
<input
id=
"toggle-all"
data-bind=
"checked: allCompleted"
type=
"checkbox"
>
<label
for=
"toggle-all"
>
Mark all as complete
</label>
<ul
id=
"todo-list"
data-bind=
"foreach: todos"
>
<ul
id=
"todo-list"
data-bind=
"foreach: filteredTodos"
>
<li
data-bind=
"css: { completed: completed, editing: editing }"
>
<div
class=
"view"
data-bind=
"event: { dblclick: $root.editItem }"
>
<input
class=
"toggle"
type=
"checkbox"
data-bind=
"checked: completed"
>
<input
class=
"toggle"
data-bind=
"checked: completed"
type=
"checkbox"
>
<label
data-bind=
"text: title"
></label>
<button
class=
"destroy"
data-bind=
"click: $root.remove"
></button>
</div>
<input
class=
"edit"
data-bind=
"value: title, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"
>
<input
class=
"edit"
data-bind=
"value: title, valueUpdate: 'afterkeydown', enterKey: $root.stopEditing, selectAndFocus: editing, event: { blur: $root.stopEditing }"
>
</li>
</ul>
</section>
...
...
@@ -35,6 +34,17 @@
<strong
data-bind=
"text: remainingCount"
>
1
</strong>
<span
data-bind=
"text: getLabel( remainingCount )"
></span>
left
</span>
<ul
id=
"filters"
>
<li>
<a
data-bind=
"css: { selected: showMode() == 'all' }"
href=
"#/all"
>
All
</a>
</li>
<li>
<a
data-bind=
"css: { selected: showMode() == 'active' }"
href=
"#/active"
>
Active
</a>
</li>
<li>
<a
data-bind=
"css: { selected: showMode() == 'completed' }"
href=
"#/completed"
>
Completed
</a>
</li>
</ul>
<button
id=
"clear-completed"
data-bind=
"visible: completedCount, click: removeCompleted"
>
Clear completed (
<span
data-bind=
"text: completedCount"
></span>
)
</button>
</footer>
</section>
...
...
@@ -46,6 +56,13 @@
</footer>
<script
src=
"../../assets/base.js"
></script>
<script
src=
"js/lib/knockout-2.0.0.js"
></script>
<!--Crossroad.js Begin -->
<script
src=
"js/lib/signals.js"
></script>
<script
src=
"js/lib/crossroads.js"
></script>
<script
src=
"js/lib/route.js"
></script>
<script
src=
"js/lib/pattern_lexer.js"
></script>
<script
src=
"js/lib/intro.js"
></script>
<!--Crossroad.js End -->
<script
src=
"js/app.js"
></script>
</body>
</body>
</html>
\ No newline at end of file
architecture-examples/knockoutjs/js/app.js
View file @
1d831397
...
...
@@ -3,21 +3,21 @@
var
ENTER_KEY
=
13
;
// trim polyfill
if
(
!
String
.
prototype
.
trim
)
{
if
(
!
String
.
prototype
.
trim
)
{
String
.
prototype
.
trim
=
function
()
{
return
this
.
replace
(
/^
\s
+|
\s
+$/g
,
''
);
return
this
.
replace
(
/^
\s
+|
\s
+$/g
,
''
);
};
}
// a custom binding to handle the enter key (could go in a separate library)
ko
.
bindingHandlers
.
enterKey
=
{
init
:
function
(
element
,
valueAccessor
,
allBindingsAccessor
,
data
)
{
init
:
function
(
element
,
valueAccessor
,
allBindingsAccessor
,
data
)
{
var
wrappedHandler
,
newValueAccessor
;
// wrap the handler with a check for the enter key
wrappedHandler
=
function
(
data
,
event
)
{
if
(
event
.
keyCode
===
ENTER_KEY
)
{
valueAccessor
().
call
(
this
,
data
,
event
);
wrappedHandler
=
function
(
data
,
event
)
{
if
(
event
.
keyCode
===
ENTER_KEY
)
{
valueAccessor
().
call
(
this
,
data
,
event
);
}
};
...
...
@@ -29,84 +29,97 @@
};
// call the real event binding's init function
ko
.
bindingHandlers
.
event
.
init
(
element
,
newValueAccessor
,
allBindingsAccessor
,
data
);
ko
.
bindingHandlers
.
event
.
init
(
element
,
newValueAccessor
,
allBindingsAccessor
,
data
);
}
};
// wrapper to hasfocus that also selects text and applies focus async
ko
.
bindingHandlers
.
selectAndFocus
=
{
init
:
function
(
element
,
valueAccessor
,
allBindingsAccessor
)
{
ko
.
bindingHandlers
.
hasfocus
.
init
(
element
,
valueAccessor
,
allBindingsAccessor
);
ko
.
utils
.
registerEventHandler
(
element
,
'
focus
'
,
function
()
{
init
:
function
(
element
,
valueAccessor
,
allBindingsAccessor
)
{
ko
.
bindingHandlers
.
hasfocus
.
init
(
element
,
valueAccessor
,
allBindingsAccessor
);
ko
.
utils
.
registerEventHandler
(
element
,
'
focus
'
,
function
()
{
element
.
select
();
}
);
});
},
update
:
function
(
element
,
valueAccessor
)
{
ko
.
utils
.
unwrapObservable
(
valueAccessor
()
);
// for dependency
update
:
function
(
element
,
valueAccessor
)
{
ko
.
utils
.
unwrapObservable
(
valueAccessor
());
// for dependency
// ensure that element is visible before trying to focus
setTimeout
(
function
()
{
ko
.
bindingHandlers
.
hasfocus
.
update
(
element
,
valueAccessor
);
},
0
);
ko
.
bindingHandlers
.
hasfocus
.
update
(
element
,
valueAccessor
);
},
0
);
}
};
// represent a single todo item
var
Todo
=
function
(
title
,
completed
)
{
this
.
title
=
ko
.
observable
(
title
);
this
.
completed
=
ko
.
observable
(
completed
);
this
.
editing
=
ko
.
observable
(
false
);
var
Todo
=
function
(
title
,
completed
)
{
this
.
title
=
ko
.
observable
(
title
);
this
.
completed
=
ko
.
observable
(
completed
);
this
.
editing
=
ko
.
observable
(
false
);
};
// our main view model
var
ViewModel
=
function
(
todos
)
{
var
ViewModel
=
function
(
todos
)
{
var
self
=
this
;
// map array of passed in todos to an observableArray of Todo objects
self
.
todos
=
ko
.
observableArray
(
ko
.
utils
.
arrayMap
(
todos
,
function
(
todo
)
{
return
new
Todo
(
todo
.
title
,
todo
.
completed
);
self
.
todos
=
ko
.
observableArray
(
ko
.
utils
.
arrayMap
(
todos
,
function
(
todo
)
{
return
new
Todo
(
todo
.
title
,
todo
.
completed
);
}));
// store the new todo value being entered
self
.
current
=
ko
.
observable
();
self
.
showMode
=
ko
.
observable
(
'
all
'
);
self
.
filteredTodos
=
ko
.
computed
(
function
()
{
switch
(
self
.
showMode
())
{
case
'
active
'
:
return
self
.
todos
().
filter
(
function
(
todo
)
{
return
!
todo
.
completed
();
});
case
'
completed
'
:
return
self
.
todos
().
filter
(
function
(
todo
)
{
return
todo
.
completed
();
});
default
:
return
self
.
todos
();
}
});
// add a new todo, when enter key is pressed
self
.
add
=
function
()
{
var
current
=
self
.
current
().
trim
();
if
(
current
)
{
self
.
todos
.
push
(
new
Todo
(
current
)
);
self
.
current
(
''
);
if
(
current
)
{
self
.
todos
.
push
(
new
Todo
(
current
));
self
.
current
(
''
);
}
};
// remove a single todo
self
.
remove
=
function
(
todo
)
{
self
.
todos
.
remove
(
todo
);
self
.
remove
=
function
(
todo
)
{
self
.
todos
.
remove
(
todo
);
};
// remove all completed todos
self
.
removeCompleted
=
function
()
{
self
.
todos
.
remove
(
function
(
todo
)
{
self
.
todos
.
remove
(
function
(
todo
)
{
return
todo
.
completed
();
});
};
// edit an item
self
.
editItem
=
function
(
item
)
{
item
.
editing
(
true
);
self
.
editItem
=
function
(
item
)
{
item
.
editing
(
true
);
};
// stop editing an item. Remove the item, if it is now empty
self
.
stopEditing
=
function
(
item
)
{
item
.
editing
(
false
);
if
(
!
item
.
title
().
trim
()
)
{
self
.
remove
(
item
);
self
.
stopEditing
=
function
(
item
)
{
item
.
editing
(
false
);
if
(
!
item
.
title
().
trim
())
{
self
.
remove
(
item
);
}
};
// count of all completed todos
self
.
completedCount
=
ko
.
computed
(
function
()
{
return
ko
.
utils
.
arrayFilter
(
self
.
todos
(),
function
(
todo
)
{
return
ko
.
utils
.
arrayFilter
(
self
.
todos
(),
function
(
todo
)
{
return
todo
.
completed
();
}
).
length
;
}).
length
;
});
// count of todos that are not complete
...
...
@@ -121,23 +134,23 @@
return
!
self
.
remainingCount
();
},
// set all todos to the written value (true/false)
write
:
function
(
newValue
)
{
ko
.
utils
.
arrayForEach
(
self
.
todos
(),
function
(
todo
)
{
write
:
function
(
newValue
)
{
ko
.
utils
.
arrayForEach
(
self
.
todos
(),
function
(
todo
)
{
// set even if value is the same, as subscribers are not notified in that case
todo
.
completed
(
newValue
);
todo
.
completed
(
newValue
);
});
}
});
// helper function to keep expressions out of markup
self
.
getLabel
=
function
(
count
)
{
return
ko
.
utils
.
unwrapObservable
(
count
)
===
1
?
'
item
'
:
'
items
'
;
self
.
getLabel
=
function
(
count
)
{
return
ko
.
utils
.
unwrapObservable
(
count
)
===
1
?
'
item
'
:
'
items
'
;
};
// internal computed observable that fires whenever anything changes in our todos
ko
.
computed
(
function
()
{
// store a clean copy to local storage, which also creates a dependency on the observableArray and all observables in each item
localStorage
.
setItem
(
'
todos-knockout
'
,
ko
.
toJSON
(
self
.
todos
)
);
localStorage
.
setItem
(
'
todos-knockout
'
,
ko
.
toJSON
(
self
.
todos
));
}).
extend
({
throttle
:
500
...
...
@@ -145,8 +158,19 @@
};
// check local storage for todos
var
todos
=
ko
.
utils
.
parseJson
(
localStorage
.
getItem
(
'
todos-knockout
'
)
);
var
todos
=
ko
.
utils
.
parseJson
(
localStorage
.
getItem
(
'
todos-knockout
'
));
// bind a new instance of our view model to the page
ko
.
applyBindings
(
new
ViewModel
(
todos
||
[]
)
);
var
viewModel
=
new
ViewModel
(
todos
||
[])
ko
.
applyBindings
(
viewModel
);
//setup crossroads
crossroads
.
addRoute
(
'
all
'
,
function
()
{
viewModel
.
showMode
(
'
all
'
);
});
crossroads
.
addRoute
(
'
active
'
,
function
()
{
viewModel
.
showMode
(
'
active
'
);
});
crossroads
.
addRoute
(
'
completed
'
,
function
()
{
viewModel
.
showMode
(
'
completed
'
);
});
window
.
onhashchange
=
function
()
{
crossroads
.
parse
(
location
.
hash
.
replace
(
"
#
"
,
""
));
};
crossroads
.
parse
(
location
.
hash
.
replace
(
"
#
"
,
""
));
})();
\ No newline at end of file
architecture-examples/knockoutjs/js/lib/crossroads.js
0 → 100644
View file @
1d831397
// Crossroads --------
//====================
/**
* @constructor
*/
function
Crossroads
()
{
this
.
_routes
=
[];
this
.
_prevRoutes
=
[];
this
.
bypassed
=
new
signals
.
Signal
();
this
.
routed
=
new
signals
.
Signal
();
}
Crossroads
.
prototype
=
{
greedy
:
false
,
greedyEnabled
:
true
,
normalizeFn
:
null
,
create
:
function
()
{
return
new
Crossroads
();
},
shouldTypecast
:
false
,
addRoute
:
function
(
pattern
,
callback
,
priority
)
{
var
route
=
new
Route
(
pattern
,
callback
,
priority
,
this
);
this
.
_sortedInsert
(
route
);
return
route
;
},
removeRoute
:
function
(
route
)
{
var
i
=
arrayIndexOf
(
this
.
_routes
,
route
);
if
(
i
!==
-
1
)
{
this
.
_routes
.
splice
(
i
,
1
);
}
route
.
_destroy
();
},
removeAllRoutes
:
function
()
{
var
n
=
this
.
getNumRoutes
();
while
(
n
--
)
{
this
.
_routes
[
n
].
_destroy
();
}
this
.
_routes
.
length
=
0
;
},
parse
:
function
(
request
,
defaultArgs
)
{
request
=
request
||
''
;
defaultArgs
=
defaultArgs
||
[];
var
routes
=
this
.
_getMatchedRoutes
(
request
),
i
=
0
,
n
=
routes
.
length
,
cur
;
if
(
n
)
{
this
.
_notifyPrevRoutes
(
routes
,
request
);
this
.
_prevRoutes
=
routes
;
//shold be incremental loop, execute routes in order
while
(
i
<
n
)
{
cur
=
routes
[
i
];
cur
.
route
.
matched
.
dispatch
.
apply
(
cur
.
route
.
matched
,
defaultArgs
.
concat
(
cur
.
params
));
cur
.
isFirst
=
!
i
;
this
.
routed
.
dispatch
.
apply
(
this
.
routed
,
defaultArgs
.
concat
([
request
,
cur
]));
i
+=
1
;
}
}
else
{
this
.
bypassed
.
dispatch
.
apply
(
this
.
bypassed
,
defaultArgs
.
concat
([
request
]));
}
},
_notifyPrevRoutes
:
function
(
matchedRoutes
,
request
)
{
var
i
=
0
,
prev
;
while
(
prev
=
this
.
_prevRoutes
[
i
++
])
{
//check if switched exist since route may be disposed
if
(
prev
.
route
.
switched
&&
this
.
_didSwitch
(
prev
.
route
,
matchedRoutes
))
{
prev
.
route
.
switched
.
dispatch
(
request
);
}
}
},
_didSwitch
:
function
(
route
,
matchedRoutes
){
var
matched
,
i
=
0
;
while
(
matched
=
matchedRoutes
[
i
++
])
{
// only dispatch switched if it is going to a different route
if
(
matched
.
route
===
route
)
{
return
false
;
}
}
return
true
;
},
getNumRoutes
:
function
()
{
return
this
.
_routes
.
length
;
},
_sortedInsert
:
function
(
route
)
{
//simplified insertion sort
var
routes
=
this
.
_routes
,
n
=
routes
.
length
;
do
{
--
n
;
}
while
(
routes
[
n
]
&&
route
.
_priority
<=
routes
[
n
].
_priority
);
routes
.
splice
(
n
+
1
,
0
,
route
);
},
_getMatchedRoutes
:
function
(
request
)
{
var
res
=
[],
routes
=
this
.
_routes
,
n
=
routes
.
length
,
route
;
//should be decrement loop since higher priorities are added at the end of array
while
(
route
=
routes
[
--
n
])
{
if
((
!
res
.
length
||
this
.
greedy
||
route
.
greedy
)
&&
route
.
match
(
request
))
{
res
.
push
({
route
:
route
,
params
:
route
.
_getParamsArray
(
request
)
});
}
if
(
!
this
.
greedyEnabled
&&
res
.
length
)
{
break
;
}
}
return
res
;
},
toString
:
function
()
{
return
'
[crossroads numRoutes:
'
+
this
.
getNumRoutes
()
+
'
]
'
;
}
};
//"static" instance
crossroads
=
new
Crossroads
();
crossroads
.
VERSION
=
'
::VERSION_NUMBER::
'
;
crossroads
.
NORM_AS_ARRAY
=
function
(
req
,
vals
)
{
return
[
vals
.
vals_
];
};
crossroads
.
NORM_AS_OBJECT
=
function
(
req
,
vals
)
{
return
[
vals
];
};
architecture-examples/knockoutjs/js/lib/intro.js
0 → 100644
View file @
1d831397
var
crossroads
,
UNDEF
;
// Helpers -----------
//====================
function
arrayIndexOf
(
arr
,
val
)
{
if
(
arr
.
indexOf
)
{
return
arr
.
indexOf
(
val
);
}
else
{
//Array.indexOf doesn't work on IE 6-7
var
n
=
arr
.
length
;
while
(
n
--
)
{
if
(
arr
[
n
]
===
val
)
{
return
n
;
}
}
return
-
1
;
}
}
function
isKind
(
val
,
kind
)
{
return
'
[object
'
+
kind
+
'
]
'
===
Object
.
prototype
.
toString
.
call
(
val
);
}
function
isRegExp
(
val
)
{
return
isKind
(
val
,
'
RegExp
'
);
}
function
isArray
(
val
)
{
return
isKind
(
val
,
'
Array
'
);
}
function
isFunction
(
val
)
{
return
typeof
val
===
'
function
'
;
}
//borrowed from AMD-utils
function
typecastValue
(
val
)
{
var
r
;
if
(
val
===
null
||
val
===
'
null
'
)
{
r
=
null
;
}
else
if
(
val
===
'
true
'
)
{
r
=
true
;
}
else
if
(
val
===
'
false
'
)
{
r
=
false
;
}
else
if
(
val
===
UNDEF
||
val
===
'
undefined
'
)
{
r
=
UNDEF
;
}
else
if
(
val
===
''
||
isNaN
(
val
))
{
//isNaN('') returns false
r
=
val
;
}
else
{
//parseFloat(null || '') returns NaN
r
=
parseFloat
(
val
);
}
return
r
;
}
function
typecastArrayValues
(
values
)
{
var
n
=
values
.
length
,
result
=
[];
while
(
n
--
)
{
result
[
n
]
=
typecastValue
(
values
[
n
]);
}
return
result
;
}
//borrowed from AMD-Utils
function
decodeQueryString
(
str
)
{
var
queryArr
=
(
str
||
''
).
replace
(
'
?
'
,
''
).
split
(
'
&
'
),
n
=
queryArr
.
length
,
obj
=
{},
item
,
val
;
while
(
n
--
)
{
item
=
queryArr
[
n
].
split
(
'
=
'
);
val
=
typecastValue
(
item
[
1
]);
obj
[
item
[
0
]]
=
(
typeof
val
===
'
string
'
)?
decodeURIComponent
(
val
)
:
val
;
}
return
obj
;
}
architecture-examples/knockoutjs/js/lib/pattern_lexer.js
0 → 100644
View file @
1d831397
// Pattern Lexer ------
//=====================
crossroads
.
patternLexer
=
(
function
()
{
var
//match chars that should be escaped on string regexp
ESCAPE_CHARS_REGEXP
=
/
[\\
.+*?
\^
$
\[\]
(){}
\/
'#
]
/g
,
//trailing slashes (begin/end of string)
LOOSE_SLASHES_REGEXP
=
/^
\/
|
\/
$/g
,
LEGACY_SLASHES_REGEXP
=
/
\/
$/g
,
//params - everything between `{ }` or `: :`
PARAMS_REGEXP
=
/
(?:\{
|:
)([^
}:
]
+
)(?:\}
|:
)
/g
,
//used to save params during compile (avoid escaping things that
//shouldn't be escaped).
TOKENS
=
{
'
OS
'
:
{
//optional slashes
//slash between `::` or `}:` or `\w:` or `:{?` or `}{?` or `\w{?`
rgx
:
/
([
:}
]
|
\w(?=\/))\/?(
:|
(?:\{\?))
/g
,
save
:
'
$1{{id}}$2
'
,
res
:
'
\\
/?
'
},
'
RS
'
:
{
//required slashes
//used to insert slash between `:{` and `}{`
rgx
:
/
([
:}
])\/?(\{)
/g
,
save
:
'
$1{{id}}$2
'
,
res
:
'
\\
/
'
},
'
RQ
'
:
{
//required query string - everything in between `{? }`
rgx
:
/
\{\?([^
}
]
+
)\}
/g
,
//everything from `?` till `#` or end of string
res
:
'
\\
?([^#]+)
'
},
'
OQ
'
:
{
//optional query string - everything in between `:? :`
rgx
:
/:
\?([^
:
]
+
)
:/g
,
//everything from `?` till `#` or end of string
res
:
'
(?:
\\
?([^#]*))?
'
},
'
OR
'
:
{
//optional rest - everything in between `: *:`
rgx
:
/:
([^
:
]
+
)\*
:/g
,
res
:
'
(.*)?
'
// optional group to avoid passing empty string as captured
},
'
RR
'
:
{
//rest param - everything in between `{ *}`
rgx
:
/
\{([^
}
]
+
)\*\}
/g
,
res
:
'
(.+)
'
},
// required/optional params should come after rest segments
'
RP
'
:
{
//required params - everything between `{ }`
rgx
:
/
\{([^
}
]
+
)\}
/g
,
res
:
'
([^
\\
/?]+)
'
},
'
OP
'
:
{
//optional params - everything between `: :`
rgx
:
/:
([^
:
]
+
)
:/g
,
res
:
'
([^
\\
/?]+)?
\
/?
'
}
},
LOOSE_SLASH
=
1
,
STRICT_SLASH
=
2
,
LEGACY_SLASH
=
3
,
_slashMode
=
LOOSE_SLASH
;
function
precompileTokens
(){
var
key
,
cur
;
for
(
key
in
TOKENS
)
{
if
(
TOKENS
.
hasOwnProperty
(
key
))
{
cur
=
TOKENS
[
key
];
cur
.
id
=
'
__CR_
'
+
key
+
'
__
'
;
cur
.
save
=
(
'
save
'
in
cur
)?
cur
.
save
.
replace
(
'
{{id}}
'
,
cur
.
id
)
:
cur
.
id
;
cur
.
rRestore
=
new
RegExp
(
cur
.
id
,
'
g
'
);
}
}
}
precompileTokens
();
function
captureVals
(
regex
,
pattern
)
{
var
vals
=
[],
match
;
while
(
match
=
regex
.
exec
(
pattern
))
{
vals
.
push
(
match
[
1
]);
}
return
vals
;
}
function
getParamIds
(
pattern
)
{
return
captureVals
(
PARAMS_REGEXP
,
pattern
);
}
function
getOptionalParamsIds
(
pattern
)
{
return
captureVals
(
TOKENS
.
OP
.
rgx
,
pattern
);
}
function
compilePattern
(
pattern
)
{
pattern
=
pattern
||
''
;
if
(
pattern
){
if
(
_slashMode
===
LOOSE_SLASH
)
{
pattern
=
pattern
.
replace
(
LOOSE_SLASHES_REGEXP
,
''
);
}
else
if
(
_slashMode
===
LEGACY_SLASH
)
{
pattern
=
pattern
.
replace
(
LEGACY_SLASHES_REGEXP
,
''
);
}
//save tokens
pattern
=
replaceTokens
(
pattern
,
'
rgx
'
,
'
save
'
);
//regexp escape
pattern
=
pattern
.
replace
(
ESCAPE_CHARS_REGEXP
,
'
\\
$&
'
);
//restore tokens
pattern
=
replaceTokens
(
pattern
,
'
rRestore
'
,
'
res
'
);
if
(
_slashMode
===
LOOSE_SLASH
)
{
pattern
=
'
\\
/?
'
+
pattern
;
}
}
if
(
_slashMode
!==
STRICT_SLASH
)
{
//single slash is treated as empty and end slash is optional
pattern
+=
'
\\
/?
'
;
}
return
new
RegExp
(
'
^
'
+
pattern
+
'
$
'
);
}
function
replaceTokens
(
pattern
,
regexpName
,
replaceName
)
{
var
cur
,
key
;
for
(
key
in
TOKENS
)
{
if
(
TOKENS
.
hasOwnProperty
(
key
))
{
cur
=
TOKENS
[
key
];
pattern
=
pattern
.
replace
(
cur
[
regexpName
],
cur
[
replaceName
]);
}
}
return
pattern
;
}
function
getParamValues
(
request
,
regexp
,
shouldTypecast
)
{
var
vals
=
regexp
.
exec
(
request
);
if
(
vals
)
{
vals
.
shift
();
if
(
shouldTypecast
)
{
vals
=
typecastArrayValues
(
vals
);
}
}
return
vals
;
}
function
interpolate
(
pattern
,
replacements
)
{
if
(
typeof
pattern
!==
'
string
'
)
{
throw
new
Error
(
'
Route pattern should be a string.
'
);
}
var
replaceFn
=
function
(
match
,
prop
){
var
val
;
if
(
prop
in
replacements
)
{
val
=
replacements
[
prop
];
if
(
match
.
indexOf
(
'
*
'
)
===
-
1
&&
val
.
indexOf
(
'
/
'
)
!==
-
1
)
{
throw
new
Error
(
'
Invalid value "
'
+
val
+
'
" for segment "
'
+
match
+
'
".
'
);
}
}
else
if
(
match
.
indexOf
(
'
{
'
)
!==
-
1
)
{
throw
new
Error
(
'
The segment
'
+
match
+
'
is required.
'
);
}
else
{
val
=
''
;
}
return
val
;
};
if
(
!
TOKENS
.
OS
.
trail
)
{
TOKENS
.
OS
.
trail
=
new
RegExp
(
'
(?:
'
+
TOKENS
.
OS
.
id
+
'
)+$
'
);
}
return
pattern
.
replace
(
TOKENS
.
OS
.
rgx
,
TOKENS
.
OS
.
save
)
.
replace
(
PARAMS_REGEXP
,
replaceFn
)
.
replace
(
TOKENS
.
OS
.
trail
,
''
)
// remove trailing
.
replace
(
TOKENS
.
OS
.
rRestore
,
'
/
'
);
// add slash between segments
}
//API
return
{
strict
:
function
(){
_slashMode
=
STRICT_SLASH
;
},
loose
:
function
(){
_slashMode
=
LOOSE_SLASH
;
},
legacy
:
function
(){
_slashMode
=
LEGACY_SLASH
;
},
getParamIds
:
getParamIds
,
getOptionalParamsIds
:
getOptionalParamsIds
,
getParamValues
:
getParamValues
,
compilePattern
:
compilePattern
,
interpolate
:
interpolate
};
}());
architecture-examples/knockoutjs/js/lib/route.js
0 → 100644
View file @
1d831397
// Route --------------
//=====================
/**
* @constructor
*/
function
Route
(
pattern
,
callback
,
priority
,
router
)
{
var
isRegexPattern
=
isRegExp
(
pattern
),
patternLexer
=
crossroads
.
patternLexer
;
this
.
_router
=
router
;
this
.
_pattern
=
pattern
;
this
.
_paramsIds
=
isRegexPattern
?
null
:
patternLexer
.
getParamIds
(
this
.
_pattern
);
this
.
_optionalParamsIds
=
isRegexPattern
?
null
:
patternLexer
.
getOptionalParamsIds
(
this
.
_pattern
);
this
.
_matchRegexp
=
isRegexPattern
?
pattern
:
patternLexer
.
compilePattern
(
pattern
);
this
.
matched
=
new
signals
.
Signal
();
this
.
switched
=
new
signals
.
Signal
();
if
(
callback
)
{
this
.
matched
.
add
(
callback
);
}
this
.
_priority
=
priority
||
0
;
}
Route
.
prototype
=
{
greedy
:
false
,
rules
:
void
(
0
),
match
:
function
(
request
)
{
request
=
request
||
''
;
return
this
.
_matchRegexp
.
test
(
request
)
&&
this
.
_validateParams
(
request
);
//validate params even if regexp because of `request_` rule.
},
_validateParams
:
function
(
request
)
{
var
rules
=
this
.
rules
,
values
=
this
.
_getParamsObject
(
request
),
key
;
for
(
key
in
rules
)
{
// normalize_ isn't a validation rule... (#39)
if
(
key
!==
'
normalize_
'
&&
rules
.
hasOwnProperty
(
key
)
&&
!
this
.
_isValidParam
(
request
,
key
,
values
)){
return
false
;
}
}
return
true
;
},
_isValidParam
:
function
(
request
,
prop
,
values
)
{
var
validationRule
=
this
.
rules
[
prop
],
val
=
values
[
prop
],
isValid
=
false
,
isQuery
=
(
prop
.
indexOf
(
'
?
'
)
===
0
);
if
(
val
==
null
&&
this
.
_optionalParamsIds
&&
arrayIndexOf
(
this
.
_optionalParamsIds
,
prop
)
!==
-
1
)
{
isValid
=
true
;
}
else
if
(
isRegExp
(
validationRule
))
{
if
(
isQuery
)
{
val
=
values
[
prop
+
'
_
'
];
//use raw string
}
isValid
=
validationRule
.
test
(
val
);
}
else
if
(
isArray
(
validationRule
))
{
if
(
isQuery
)
{
val
=
values
[
prop
+
'
_
'
];
//use raw string
}
isValid
=
arrayIndexOf
(
validationRule
,
val
)
!==
-
1
;
}
else
if
(
isFunction
(
validationRule
))
{
isValid
=
validationRule
(
val
,
request
,
values
);
}
return
isValid
;
//fail silently if validationRule is from an unsupported type
},
_getParamsObject
:
function
(
request
)
{
var
shouldTypecast
=
this
.
_router
.
shouldTypecast
,
values
=
crossroads
.
patternLexer
.
getParamValues
(
request
,
this
.
_matchRegexp
,
shouldTypecast
),
o
=
{},
n
=
values
.
length
,
param
,
val
;
while
(
n
--
)
{
val
=
values
[
n
];
if
(
this
.
_paramsIds
)
{
param
=
this
.
_paramsIds
[
n
];
if
(
param
.
indexOf
(
'
?
'
)
===
0
&&
val
)
{
//make a copy of the original string so array and
//RegExp validation can be applied properly
o
[
param
+
'
_
'
]
=
val
;
//update vals_ array as well since it will be used
//during dispatch
val
=
decodeQueryString
(
val
);
values
[
n
]
=
val
;
}
o
[
param
]
=
val
;
}
//alias to paths and for RegExp pattern
o
[
n
]
=
val
;
}
o
.
request_
=
shouldTypecast
?
typecastValue
(
request
)
:
request
;
o
.
vals_
=
values
;
return
o
;
},
_getParamsArray
:
function
(
request
)
{
var
norm
=
this
.
rules
?
this
.
rules
.
normalize_
:
null
,
params
;
norm
=
norm
||
this
.
_router
.
normalizeFn
;
// default normalize
if
(
norm
&&
isFunction
(
norm
))
{
params
=
norm
(
request
,
this
.
_getParamsObject
(
request
));
}
else
{
params
=
this
.
_getParamsObject
(
request
).
vals_
;
}
return
params
;
},
interpolate
:
function
(
replacements
)
{
var
str
=
crossroads
.
patternLexer
.
interpolate
(
this
.
_pattern
,
replacements
);
if
(
!
this
.
_validateParams
(
str
)
)
{
throw
new
Error
(
'
Generated string doesn
\'
t validate against `Route.rules`.
'
);
}
return
str
;
},
dispose
:
function
()
{
this
.
_router
.
removeRoute
(
this
);
},
_destroy
:
function
()
{
this
.
matched
.
dispose
();
this
.
switched
.
dispose
();
this
.
matched
=
this
.
switched
=
this
.
_pattern
=
this
.
_matchRegexp
=
null
;
},
toString
:
function
()
{
return
'
[Route pattern:"
'
+
this
.
_pattern
+
'
", numListeners:
'
+
this
.
matched
.
getNumListeners
()
+
'
]
'
;
}
};
architecture-examples/knockoutjs/js/lib/signals.js
0 → 100644
View file @
1d831397
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment