66class Walkify {
77 constructor ( routesObj , viewElem ) {
88 this . routes = routesObj ;
9+ this . supportedPrefix = [
10+ '!' , '$' , '%' , '^' , '*' , '_' , '+' , '~' , '-' , '`' , ':' , '@' , '#'
11+ ] ;
12+ this . variableBracketsStart = '{{' ;
13+ this . variableBracketsEnd = '}}' ;
14+ this . variablePrefix = "\\$" ;
915 this . viewElem = viewElem ? this . mount ( viewElem ) : null ;
10- this . init ( ) ;
1116 }
1217 init ( ) {
1318 window . onload = ( ) => {
@@ -16,21 +21,24 @@ class Walkify {
1621 window . onhashchange = ( ) => {
1722 this . navigate ( ) ;
1823 }
24+ this . setLinksHandler ( ) ;
25+ }
26+ setLinksHandler ( ) {
1927 let links = document . getElementsByTagName ( 'a' ) ;
2028 [ ... links ] . forEach ( ( link ) => {
2129 let linkHref = link . getAttribute ( 'href' ) ;
22- linkHref = linkHref . startsWith ( '#' ) ? linkHref : '#' + linkHref ;
2330 let isRouteLink = ! ( / w w w | h t t p | h t t p s | f t p / ig. test ( linkHref ) ) ;
2431 if ( link . hasAttribute ( 'href' ) && isRouteLink ) {
2532 link . addEventListener ( 'click' , ( event ) => {
33+ linkHref = linkHref . startsWith ( '#' ) ? linkHref : '#' + linkHref ;
2634 event . preventDefault ( ) ;
2735 if ( location . hash != linkHref ) {
2836 'exist' in this . currentRoute && this . currentRoute . exist ( ) ;
29- this . routeTo ( linkHref )
37+ this . routeTo ( linkHref ) ;
3038 }
3139 } )
3240 }
33- } )
41+ } ) ;
3442 }
3543 navigate ( ) {
3644 if ( this . getHash ( ) ) {
@@ -65,6 +73,8 @@ class Walkify {
6573 let attribute = viewElem [ 0 ] == '.' ? 'classname' : 'id' ;
6674 if ( ! rootElem ) throw new Error ( 'could not found element with ' + attribute + ' ' + viewElem . slice ( 1 ) ) ;
6775 this . viewElem = rootElem ;
76+ //start listening to changes in url
77+ this . init ( ) ;
6878 return this ;
6979 }
7080 route ( ) {
@@ -76,17 +86,17 @@ class Walkify {
7686 this . routes [ key ] . matched ( ) ;
7787 this . currentRoute = this . routes [ key ] ;
7888 } else {
79- let Lkey = this . findObj ( key ) ;
80- if ( key && Lkey ) {
89+ let routeStr = this . findRoute ( key ) ;
90+ if ( key && routeStr ) {
8191 let keyArr = key . split ( '/' ) ;
82- let keyArrObj = ( Lkey . split ( '/' ) ) ;
92+ let keyArrObj = ( routeStr . split ( '/' ) ) ;
8393 let isTheSame = this . compare ( keyArr , keyArrObj ) ;
8494 if ( isTheSame ) {
85- if ( 'matched' in this . routes [ Lkey ] ) {
95+ if ( 'matched' in this . routes [ routeStr ] ) {
8696 let newObj = this . buildObj ( keyArr , keyArrObj ) ;
87- this . routes [ Lkey ] . view = this . view . bind ( this , this . routes [ Lkey ] ) ;
88- this . routes [ Lkey ] . matched . apply ( this . routes [ Lkey ] , Object . values ( newObj ) ) ;
89- this . currentRoute = this . routes [ Lkey ] ;
97+ this . routes [ routeStr ] . view = this . view . bind ( this , this . routes [ routeStr ] ) ;
98+ this . routes [ routeStr ] . matched . apply ( this . routes [ routeStr ] , Object . values ( newObj ) ) ;
99+ this . currentRoute = this . routes [ routeStr ] ;
90100 return this ;
91101 }
92102 throw new Error ( 'walkify expected hook "matched" not found!' )
@@ -104,101 +114,88 @@ class Walkify {
104114 }
105115 return this ;
106116 }
107- compare ( arr1 , arr2 ) {
108- let len = arr1 . length == arr2 . length ;
109- let p = arr2 . slice ( 1 , - 1 ) . every ( ( el ) => {
110- return el . includes ( '{' ) && el . includes ( '}' ) && ! ( el . includes ( ':' ) )
111- } ) ;
112- if ( arr1 . length && arr2 . length ) {
113- if ( arr2 . includes ( '*' ) ) {
114- let str = arr2 . join ( '/' ) . replace ( '*' , '' ) ;
115- let str2 = arr1 . join ( '/' ) ;
116- if ( str2 . indexOf ( str ) != - 1 ) {
117- return true
118- }
119- } else {
117+ compare ( hashArr , routesArr ) {
118+ if ( hashArr . length == routesArr . length ) {
119+ if ( routesArr . indexOf ( '*' ) == - 1 ) {
120120 let bool = false ;
121- let b = arr2 . map ( ( el , i ) => {
121+ let b = routesArr . map ( ( el , i ) => {
122122 if ( el . includes ( '{' ) && el . includes ( '}' ) ) {
123- el = el . slice ( 1 , - 1 ) ;
123+ el = el . slice ( 1 , - 1 ) ;
124124 }
125125 if ( el . includes ( ':' ) ) {
126126 bool = true ;
127- let regItem = el . split ( ':' ) [ 1 ] ;
128- let reg = new RegExp ( '^' + regItem + '$' ) ;
129- return ( reg . test ( arr1 [ i ] ) ) ;
127+ let regexString = el . split ( ':' ) [ 1 ] ;
128+ let regex = new RegExp ( '^' + regexString + '$' ) ;
129+ return ( regex . test ( hashArr [ i ] ) ) ;
130130 } else {
131- return ( el == arr1 [ i ] ) ;
131+ return ( el == hashArr [ i ] ) ;
132132 }
133133 } ) ;
134+
134135 b = b . every ( ( el ) => {
135136 return el == true ;
136137 } ) ;
137- if ( ! bool ) return len && arr1 . join ( '' ) == arr2 . join ( '' ) ;
138- if ( p ) return p && len ;
139- return b && len ;
140- }
141- }
142- if ( p && arr1 . length == arr2 . length ) {
143- return true ;
144- } ;
145- return false ;
138+ return b ;
139+
140+ } else {
141+ // return (hashArr.join('').startsWith(routesArr.join('').slice(0, -1)));
142+ }
143+ } else {
144+ return ( hashArr . join ( '' ) . startsWith ( routesArr . join ( '' ) . slice ( 0 , - 1 ) ) ) ;
145+ }
146146 }
147- findObj ( urlH ) {
148- let routesArr = Object . keys ( this . routes ) ;
149- let hashArr = urlH . split ( '/' ) ;
150- let arr ;
151- let key ;
152- for ( let i = 0 ; i < routesArr . length ; i ++ ) {
153- let listenArr = routesArr [ i ] . split ( '/' ) ;
154- let h = [ ] ;
155- arr = [ ...hashArr ] ;
156- if ( routesArr [ i ] == '*' || routesArr [ i ] == '!' ) {
157- continue ;
147+ closestRoute ( routesArr , hashArr ) {
148+ let matchCountObj = { } ;
149+ routesArr . forEach ( ( route ) => {
150+ matchCountObj [ route ] = {
151+ path : route ,
152+ count : 0
158153 }
159- let a = listenArr ;
160- let b = a . filter ( ( el ) => {
161- return ! ( el . includes ( '{' ) )
162- } ) ;
163- for ( let i = 0 ; i < arr . length ; i ++ ) {
164- for ( let v = 0 ; v < b . length ; v ++ ) {
165- if ( arr [ i ] == b [ v ] ) {
166- h . push ( arr [ i ] ) ;
167- }
168- }
154+ let currentRouteArr = route . split ( '/' ) ;
155+ for ( let i = 0 ; i < currentRouteArr . length ; i ++ ) {
156+ if ( ( ! ! currentRouteArr [ i ] ) && hashArr [ i ] == currentRouteArr [ i ] ) {
157+ matchCountObj [ route ] [ 'count' ] += 1 ;
158+ }
169159 }
170- if ( JSON . stringify ( b ) == JSON . stringify ( h ) ) {
171- let bool = this . compare ( hashArr , listenArr ) ;
172- if ( bool ) {
173- key = routesArr [ i ] ;
174- }
160+ } ) ;
161+ let matchCountArr = Object . keys ( matchCountObj ) ;
162+ if ( matchCountArr . length ) {
163+ let Highestcount = Math . max . apply ( null , matchCountArr . map ( ( route ) => {
164+ return ( matchCountObj [ route ] [ 'count' ] ) ;
165+ } ) ) ;
166+ let closeRoute = matchCountArr . filter ( ( route ) => {
167+ return ( matchCountObj [ route ] [ 'count' ] == Highestcount ) ;
168+ } ) [ 0 ] ;
169+ let closeRouteArr = closeRoute . split ( '/' ) ;
170+ if ( closeRoute [ 0 ] . indexOf ( '*' ) == - 1 ) {
171+ // let passed = closeRouteArr.length;
172+ if ( closeRouteArr . length == Highestcount ) return closeRoute ;
175173 }
176- else if ( this . compare ( hashArr , listenArr ) ) {
177- let bool = this . compare ( hashArr , listenArr ) ;
178- if ( bool ) {
179- key = routesArr [ i ] ;
180- }
181- return key ;
182- }
183- else {
184- if ( hashArr . length == listenArr . length ) {
185- let p = listenArr . slice ( 1 , - 1 ) . every ( ( el ) => {
186- if ( el . includes ( '{' ) && el . includes ( '}' ) && ! ( el . includes ( ':' ) ) ) {
187- key = routesArr [ i ] ;
188- }
189- } ) ;
190- return key ;
191- }
192- if ( b . includes ( '*' ) ) {
193- let k = ( routesArr [ i ] . slice ( 0 , - 1 ) ) ;
194- let bool = this . compare ( hashArr , k . split ( '/' ) ) ;
195- if ( bool ) {
196- key = routesArr [ i ] ;
197- }
198- }
174+ if ( closeRoute . indexOf ( '*' ) != - 1 ) {
175+ let astIndex = closeRouteArr . lastIndexOf ( '*' ) ;
176+ let slicedHash = hashArr . slice ( 0 , astIndex ) ;
177+ if ( closeRouteArr . slice ( 0 , - 1 ) . join ( '/' ) == slicedHash . join ( '/' ) ) return closeRoute
199178 }
179+ if ( / { ( .+ ?) : ( .+ ?) } / . test ( closeRoute ) ) {
180+ return closeRoute ;
181+ }
182+ }
183+ }
184+ findRoute ( urlHash ) {
185+ let routes = this . routes ;
186+ let hashArr = urlHash . split ( '/' ) ;
187+ let routesArr = Object . keys ( routes ) ;
188+ let routesWithSameLength = routesArr . filter ( ( route ) => {
189+ return route . split ( '/' ) . length == hashArr . length ;
190+ } ) ;
191+ let routesWithAsteriks = routesArr . filter ( ( route ) => {
192+ return route . endsWith ( '*' ) ;
193+ } ) ;
194+ if ( routesWithSameLength . length ) {
195+ return this . closestRoute ( routesWithSameLength , hashArr ) ;
196+ } else {
197+ return this . closestRoute ( routesWithAsteriks , hashArr ) ;
200198 }
201- return key ? key : false ;
202199 }
203200 buildObj ( arr1 , arr2 ) {
204201 let obj = { } ;
@@ -221,36 +218,81 @@ class Walkify {
221218 } ) ;
222219 return ( obj ) ;
223220 }
221+ hasTemplate ( templates , viewObj ) {
222+ return templates . some ( ( template ) => {
223+ return template . getAttribute ( 'for' ) == viewObj . name ;
224+ } ) ;
225+ }
224226 view ( viewObj , data = { } , temp ) {
225227 if ( ! temp || data . redirect ) {
226228 if ( ! ( 'name' in viewObj ) ) throw new Error ( 'property "name" missing in route object' ) ;
227- let templates = document . getElementsByTagName ( 'template' ) ;
228- [ ... templates ] . forEach ( ( template ) => {
229+ let templates = [ ... document . getElementsByTagName ( 'template' ) ] ;
230+ if ( ! this . hasTemplate ( templates , viewObj ) ) throw new Error ( 'template not found for current page' ) ;
231+ templates . forEach ( ( template ) => {
229232 if ( template . getAttribute ( 'for' ) == viewObj . name ) {
230233 this . mountView ( data . redirect ? data . data : data , template ) ;
231234 'mounted' in viewObj && viewObj . mounted ( ) ;
235+ this . setLinksHandler ( ) ;
232236 }
233237 } , this ) ;
234238 } else {
235239 this . mountView ( data , temp ) ;
236240 'mounted' in viewObj && viewObj . mounted ( ) ;
241+ this . setLinksHandler ( ) ;
237242 }
238243 }
239244 mountView ( data , template ) {
245+ let hasNoPrefix = this . noVariablePrefix ;
240246 template = ( typeof template != 'string' ) ? template . innerHTML : template ;
241- template = template . replace ( / \$ { { ( .+ ?) } } / ig, matched => {
242- let key = matched . slice ( 3 , - 2 ) . trim ( ) ;
247+
248+ let regex = '[' + ( hasNoPrefix ? this . supportedPrefix . join ( '|' ) : this . variablePrefix ) + ']' + ( hasNoPrefix ? '*' : '+' ) + '?' + ( ( this . variableBracketsStart ) + '+' ) + '(.+?)' + ( ( this . variableBracketsEnd ) + '+' ) ;
249+
250+ template = template . replace ( new RegExp ( regex , 'ig' ) , matched => {
251+ let key = ( matched ) . slice ( this . variableBracketsStart . length + ( hasNoPrefix ? 0 : this . variablePrefix . length - 1 ) , - this . variableBracketsEnd . length ) . trim ( ) ;
252+ let hasCharLeft = ( key . indexOf ( this . variableBracketsEnd [ 0 ] ) != - 1 ) || ( key . indexOf ( this . variableBracketsStart [ 0 ] ) != - 1 ) ;
253+
243254 if ( key in data ) {
244255 return data [ key ] ;
245256 } else {
246257 try {
247258 return ( eval ( key ) ) ;
248259 } catch ( e ) {
249- console . error ( 'property : "' + key + '" not defined' ) ;
250- return undefined
260+ if ( ! hasCharLeft ) console . error ( 'property : "' + key + '" not defined' ) ;
261+ return hasCharLeft ? matched : undefined
251262 }
252263 }
253264 } ) ;
254265 this . viewElem . innerHTML = template ;
255266 }
267+
268+
269+ //methods to resolve external library conflict
270+ //changes the default '$' to the argument passed
271+
272+ setPrefix ( variablePrefix ) {
273+ variablePrefix = ! variablePrefix ? variablePrefix : variablePrefix . trim ( ) ;
274+ let isSupported = this . supportedPrefix . indexOf ( variablePrefix ) != - 1 ;
275+
276+ if ( ! isSupported && variablePrefix !== false ) throw new Error ( 'prefix not supported' ) ;
277+ if ( ( variablePrefix === false ) || ! variablePrefix ) {
278+ this . noVariablePrefix = true ;
279+ }
280+ this . variablePrefix = '\\' + variablePrefix ;
281+ }
282+ //changes the default curly braces '{{' and '}}'
283+ setVariableBrackets ( brackets ) {
284+ if ( this . typeof ( brackets ) != 'Array' ) {
285+ throw new Error ( 'parameter for setVariableBrackets method must be of the type Array' )
286+ } else if ( brackets . length > 2 ) {
287+ throw new Error ( 'array should not be greater than two' ) ;
288+ } else if ( this . typeof ( brackets [ 0 ] ) != 'String' || this . typeof ( brackets [ 0 ] ) != 'String' ) {
289+ throw new Error ( 'character types in the array must be string' )
290+ } else {
291+ this . variableBracketsStart = brackets [ 0 ] ;
292+ this . variableBracketsEnd = brackets [ 1 ] ;
293+ }
294+ }
295+ typeof ( variable ) {
296+ return ( variable ) . constructor . name ;
297+ }
256298}
0 commit comments