Knockoutjsред рдПрдХ рдкреЗрдбрд╝ "рдмрдврд╝рддрд╛"


рд▓реЗрдЦреЛрдВ рдХреА рдЖрд╡реГрддреНрддрд┐ рдХреЛ рджреЗрдЦрддреЗ рд╣реБрдП, рдиреЙрдХрдЖрдЙрдЯ рдЬреЗрдПрд╕ рд╣реИрдмреЗ рдкрд░ рд▓реЛрдХрдкреНрд░рд┐рдпрддрд╛ рдкреНрд░рд╛рдкреНрдд рдХрд░ рд░рд╣рд╛ рд╣реИред рдореИрдВ рднреА рдЕрдкрдирд╛ рдпреЛрдЧрджрд╛рди рджреВрдВрдЧрд╛ред рдореИрдВ рд╡рд┐рд╢реЗрд╖ рд░реВрдк рд╕реЗ рдЧреИрд░-рдорд╛рдирдХ HTML рдирд┐рдпрдВрддреНрд░рдг рдФрд░ "рдЯреНрд░реА" рдХреЗ рд╡рд┐рд╖рдп рдХреЛ рдЙрдЬрд╛рдЧрд░ рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВред рдпрд╣рд╛рдВ рдкреЗрдбрд╝ рдХреЗ рджреНрд╡рд╛рд░рд╛ рдЯреНрд░реАрд╡реНрдпреВ рдирд┐рдпрдВрддреНрд░рдг рдХрд╛ рдПрдХ рдПрдирд╛рд▓реЙрдЧ рд╣реИред рд▓реЗрдЦ рдорд╛рдирддрд╛ рд╣реИ рдХрд┐ рдкрд╛рдардХ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдПрдХ рдмреБрдирд┐рдпрд╛рджреА рд╕реНрддрд░ рдкрд░ рдиреЙрдХрдЖрдЙрдЯ рдЬреЗрдПрд╕ рд╕реЗ рдкрд░рд┐рдЪрд┐рдд рд╣реИред рдкреНрд░рдХрд╛рд╢рди рдХреЛ рдиреЙрдЪрдЖрдЙрдЯ рдЬреЗрдПрд╕ рд╕реАрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рдЯреНрдпреВрдЯреЛрд░рд┐рдпрд▓ рдХреЗ рд░реВрдк рдореЗрдВ рджреЗрдЦрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред рджреВрд╕рд░реА рдУрд░, рдореБрдЭреЗ рдЙрдореНрдореАрдж рд╣реИ рдХрд┐ рдиреЙрдХрдЖрдЙрдЯ рдЬреЗрдПрд╕ рдХреЗ рдЕрдиреБрднрд╡реА рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЕрдкрдиреЗ рд▓рд┐рдП рдХреБрдЫ рдирдпрд╛ рд╕реАрдЦ рд╕рдХреЗрдВрдЧреЗред

рдЯреНрд░реАрд╡реНрдпреВ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХрдИ рдкреБрд╕реНрддрдХрд╛рд▓рдп рд▓рд┐рдЦреЗ рдЧрдП рд╣реИрдВред рдФрд░ рдиреЙрдХрдЖрдЙрдЯрдЬреЗрдПрд╕ рдХреЗ рд╕рд╛рде рдорд┐рд▓рдХрд░ рддреАрд╕рд░реЗ рдкрдХреНрд╖ рдХреЗ рдкреБрд╕реНрддрдХрд╛рд▓рдпреЛрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдиреЙрдХрдЖрдЙрдЯ рдЬреЗрдПрд╕ рдХреЗ рд▓рд┐рдП рд╕рдВрдмрдВрдзрд┐рдд рдмрд╛рдЗрдВрдбрд┐рдВрдЧ рдкрд╛рд░рдВрдкрд░рд┐рдХ рд░реВрдк рд╕реЗ рдмрдирд╛рдИ рдЧрдИ рд╣реИрдВред рджреБрд░реНрднрд╛рдЧреНрдп рд╕реЗ, рдЕрдХреНрд╕рд░ рдЯреНрд░реА рд╡реНрдпреВ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдмрд╣реБрдд рдмрдбрд╝реА рд╣реЛрддреА рд╣реИрдВ, рдЬрд┐рдирдореЗрдВ рдХрд╛рд░реНрдпрдХреНрд╖рдорддрд╛ рдХреА рдЕрдзрд┐рдХрддрд╛ рд╣реЛрддреА рд╣реИ, рдЕрдХреНрд╕рд░ рдЕрдкрдиреЗ рдбреЗрдЯрд╛ рдореЙрдбрд▓ рдХреЛ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХреЗ рдЕрдиреБрдХреВрд▓ рдмрдирд╛рдирд╛ рдЖрд╡рд╢реНрдпрдХ рд╣реЛрддрд╛ рд╣реИред рдпрджрд┐ рдЖрдкрдХреЛ рдиреЙрдХрдЖрдЙрдЯ рдЬреЗрдПрд╕ рдХреЗ рд╕рд╛рде рдЯреНрд░реА рд╡реНрдпреВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рддреЛ рдкреНрд░реЛрдЧреНрд░рд╛рдорд░ рд╕рд╣реА рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдФрд░ рдмрд╛рдЗрдВрдбрд┐рдВрдЧ рдХреА рдЦреЛрдЬ рдХрд░рддрд╛ рд╣реИред рд╣рдореЗрд╢рд╛ рдкрд╛рдпрд╛ рдЧрдпрд╛ рдкреБрд╕реНрддрдХрд╛рд▓рдп рдХреЗ рд▓рд┐рдП рдПрдХ рддреИрдпрд╛рд░ рдмрдВрдзрди рдирд╣реАрдВ рд╣реИ, рдЗрд╕рд▓рд┐рдП рдЖрдкрдХреЛ рдЕрдкрдирд╛ рд╕реНрд╡рдпрдВ рдХрд╛ рдмрдВрдзрди рдмрдирд╛рдирд╛ рд╣реЛрдЧрд╛, рдФрд░ рдпрд╣рд╛рдВ рд╕рдмрд╕реЗ рджрд┐рд▓рдЪрд╕реНрдк рдмрд╛рдд рд╢реБрд░реВ рд╣реЛрддреА рд╣реИ - рдкреБрд╕реНрддрдХрд╛рд▓рдп рдХреЗ рдЗрдирд╕рд╛рдЗрдб рдХрд╛ рдЕрдзреНрдпрдпрди, рдЬреЛ рд╣рдореЗрд╢рд╛ рд╕реБрдЦрдж рдирд╣реАрдВ рд╣реЛрддрд╛ рд╣реИред рд▓реЗрдХрд┐рди рдореИрдВ рдмрд╕ рдпрд╣ рдХрд╛рдо рдХрд░рдирд╛ рдЪрд╛рд╣рддрд╛ рд╣реВрдВ ... рдПрдХ рд╡реИрдХрд▓реНрдкрд┐рдХ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдпрд╣рд╛рдВ рдкреНрд░рд╕реНрддрд╛рд╡рд┐рдд рд╣реИ - рдЯреНрд░реАрд╡реЙрдХ рдХреЛ рд╕реАрдзреЗ рдиреЙрдХрдЖрдЙрдЯ рдЬреЗрдПрд╕ рдкрд░ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдПред

рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдбреЗрдЯрд╛ рдмрдВрдзрди рдХреЗ рдмрд┐рдирд╛, рдПрдХ рдЕрдореВрд░реНрдд рдкреЗрдбрд╝ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░реЗрдВрдЧреЗред рдЬрдм рдЖрдк рдЕрдкрдиреЗ рдкреЗрдбрд╝реЛрдВ рдХреЛ "рдЙрдЧрд╛ "реЗрдВрдЧреЗ рддреЛ рдЖрдк рд╡рд╛рд╕реНрддрд╡рд┐рдХ рдЖрдВрдХрдбрд╝реЛрдВ рд╕реЗ рдЦреБрдж рдХреЛ рдмрд╛рдВрдз рдкрд╛рдПрдВрдЧреЗред рдпрд╛ рдпрд╣ рд╕рдорд╕реНрдпрд╛ рдиреЙрдХрдЖрдЙрдЯ рдЬреЗрдПрд╕ рдХреЛрдб рдХреЗ рдкреБрди: рдЙрдкрдпреЛрдЧ рдХреЗ рд▓рд┐рдП рдореЗрд░реЗ рд░рд╛рд╕реНрддреЗ рд╕реЗ рд╣рд▓ рд╣реЛ рдЬрд╛рдПрдЧреА, рдЬрд┐рд╕реЗ рдореИрдВ рдЕрдВрдд рдореЗрдВ рджрд┐рдЦрд╛рдКрдВрдЧрд╛ред

... рдФрд░ рдПрдХ рдкреЗрдбрд╝ рдирд╣реАрдВ, рдмрд▓реНрдХрд┐ рдПрдХ рд╕реВрдЪреАред


рдкрд░рдВрдкрд░рд╛рдЧрдд рд░реВрдк рд╕реЗ, HTML рдкреЗрдбрд╝реЛрдВ рдХреЛ рдиреЗрд╕реНрдЯреЗрдб рдЕрдирдСрд░реНрдбреЗрдб рд▓рд┐рд╕реНрдЯ (рдпреВрдПрд▓ рдЯреИрдЧ) рдФрд░ рд╕реАрдПрд╕рдПрд╕ рд╢реИрд▓рд┐рдпреЛрдВ рдХреЗ рд╕реЗрдЯ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдмрдирд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдпрд╛рдиреА рдПрдХ рдкреЗрдбрд╝ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдЖрдкрдХреЛ рд▓рдЧрднрдЧ рдирд┐рдореНрди HTML рдорд╛рд░реНрдХрдЕрдк рдЬрдирд░реЗрдЯ рдХрд░рдирд╛ рд╣реЛрдЧрд╛:
<ul> <li> 1 <ul> <li> 3</li> </ul> </li> <li> 2 </li> <ul> 


рдиреЛрдб рдХреА рдЙрдкрд╕реНрдерд┐рддрд┐ рдХреЛ рд╕реАрдПрд╕рдПрд╕ рдХрдХреНрд╖рд╛рдУрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдХреЙрдиреНрдлрд╝рд┐рдЧрд░ рдХрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИред
рдкреНрд░рджрд░реНрд╢рди рдореЙрдбрд▓ (ViewModel), рд╕реНрдкрд╖реНрдЯ рд░реВрдк рд╕реЗ, рджреЛ рд╡рд╕реНрддреБрдУрдВ рд╕реЗ рдмрдирд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ - TreeViewNode рдФрд░ TreeView рд╕реЗ рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП:
 function TreeViewNode(caption,children){ this.caption = caption; this.children = children; } function TreeView(children){ this.children = children; }} 

рдХреЗрд╡рд▓ рдПрдХ TreeViewNode рдХреЗ рд╕рд╛рде рдХрд░рдиреЗ рдХрд╛ рдкреНрд░рд▓реЛрднрди рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ TreeView рдХреЗрд╡рд▓ рдХреИрдкреНрд╢рди рдлрд╝реАрд▓реНрдб рдХреА рдЕрдиреБрдкрд╕реНрдерд┐рддрд┐ рдореЗрдВ рднрд┐рдиреНрди рд╣реЛрддрд╛ рд╣реИред рд╣рд╛рд▓рд╛рдВрдХрд┐, рдЗрд╕рдореЗрдВ рдЬрд▓реНрджрдмрд╛рдЬреА рди рдХрд░реЗрдВ, рдХреНрдпреЛрдВрдХрд┐ рдмрд╛рдж рдореЗрдВ рдЗрди рд╡рд╕реНрддреБрдУрдВ рдореЗрдВ рдмрд╣реБрдд рдЕрдзрд┐рдХ рдЕрдВрддрд░ рд╣реЛрдЧрд╛ред
рдореЙрдбрд▓-рдмрд╛рдЙрдВрдб рдорд╛рд░реНрдХрдЕрдк рдПрдХ рдкреБрдирд░рд╛рд╡рд░реНрддреА рдкреИрдЯрд░реНрди рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдЧрд╛:
 <div data-bind='with: tree'> <ul data-bind='template: {name:"treeNode", foreach: children}'> </ul> </div> <script id='treeNode' type='text/html'> <li> <span data-bind='text:caption'></span> <ul data-bind='template: {name:"treeNode", foreach: children}'> </ul> </li> </script> 


рдмреЗрд╢рдХ, рд╣рдореЗрдВ рдЕрдкрдиреЗ рдореЙрдбрд▓ рдХреЛ рдбреЗрдЯрд╛ рдХреЗ рд╕рд╛рде рдкреЙрдкреНрдпреБрд▓реЗрдЯ рдХрд░рдиреЗ рдФрд░ рдмрд╛рдЗрдВрдбрд┐рдВрдЧ рдХреЛ рдЖрд░рдВрдн рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ:
 var vm = { tree: new TreeView([ new TreeViewNode('Node 1',[ new TreeViewNode('Node 3') ]), new TreeViewNode('Node 2') ]) }; ko.applyBindings(vm); 


рдпрд╣рд╛рдБ рд╣рдореЗрдВ рдХреНрдпрд╛ рдорд┐рд▓рд╛ рд╣реИ:
рдЫрд╡рд┐
рдкреНрд░рдпреЛрдЧ рдХреЗ рд▓рд┐рдП JSFiddle ред

рдЕрдм рддрдХ рдпрд╣ рдЯреНрд░реАрд╡реНрдпреВ рдЬреИрд╕рд╛ рдирд╣реАрдВ рд╣реИред рдЖрдкрдХреЛ рд╕реАрдПрд╕рдПрд╕ рдХрдХреНрд╖рд╛рдУрдВ рдХреЛ рдирд┐рд░реНрджрд┐рд╖реНрдЯ рдХрд░рдиреЗ рдФрд░ рдЙрдЪрд┐рдд рд╢реИрд▓рд┐рдпреЛрдВ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред

рд╕реНрдерд┐рддрд┐ рдХрд╛ рдкреНрд░рддрд┐рдмрд┐рдВрдмред


рдХрдХреНрд╖рд╛рдУрдВ рдХреЛ рдкреЗрдбрд╝ рдХреЗ рдиреЛрдб рдХреА рд╕реНрдерд┐рддрд┐ рдФрд░ рд╕реНрдерд┐рддрд┐ рдХреЛ рдкреНрд░рддрд┐рдмрд┐рдВрдмрд┐рдд рдХрд░рдирд╛ рдЪрд╛рд╣рд┐рдПред рдиреЛрдб рдХреА рд╕реНрдерд┐рддрд┐ рдореЙрдбрд▓ рдореЗрдВ рдкрд░рд┐рд▓рдХреНрд╖рд┐рдд рд╣реЛрдиреА рдЪрд╛рд╣рд┐рдП, рдЗрд╕рд▓рд┐рдП рдЪрд▓реЛ рдореЙрдбрд▓ рдкрд░ рдЪрд▓рддреЗ рд╣реИрдВред
рдПрдХ рдиреЛрдб рдХрд╛ рд╡рд┐рд╕реНрддрд╛рд░ рдпрд╛ рдкрддрди рд╣реЛ рд╕рдХрддрд╛ рд╣реИред рдПрдХ рд╕реЗ рджреВрд╕рд░реЗ рд░рд╛рдЬреНрдп рдХреЛ рдПрдХ рд╕рд░рд▓ рдСрдкрд░реЗрд╢рди рджреНрд╡рд╛рд░рд╛ рдкреНрд░рд╛рдкреНрдд рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рд╕реБрд╡рд┐рдзрд╛ рдХреЗ рд▓рд┐рдП рд╣рдо рджреЛрдиреЛрдВ рдХреЛ рдкрд░рд┐рднрд╛рд╖рд┐рдд рдХрд░рддреЗ рд╣реИрдВ:

 function TreeViewNode(caption,children){ ... this.isOpen = ko.observable(); this.isClosed = ko.computed(function(){ return !this.isOpen(); },this); ... } 

рдореИрдВрдиреЗ .isOpen, .isClosed рдЧреБрдгреЛрдВ рдХреЛ рдЕрд╡рд▓реЛрдХрдиреАрдп рдмрдирд╛ рджрд┐рдпрд╛, рдХреНрдпреЛрдВрдХрд┐ рд╡реЗ рдПрдХ-рджреВрд╕рд░реЗ рдкрд░ рдирд┐рд░реНрднрд░ рдХрд░рддреЗ рд╣реИрдВ, рдФрд░ рдЙрдиреНрд╣реЗрдВ рдмрджрд▓рдиреЗ рд╕реЗ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ DOM рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрди рд╣реЛрдЧрд╛ред рдЗрди рдЧреБрдгреЛрдВ рдХреЛ рдмрджрд▓рдХрд░, рд╣рдо рдкреЗрдбрд╝ рдХреЗ рдиреЛрдбреНрд╕ рдХрд╛ рд╡рд┐рд╕реНрддрд╛рд░ / рдкрддрди рдХрд░реЗрдВрдЧреЗред
рдореИрдВрдиреЗ .isClosed рд╕рдВрдкрддреНрддрд┐ рдХреЛ рдХреЗрд╡рд▓ рдкрдврд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдирд╛рдпрд╛ рд╣реИ, рддрд╛рдХрд┐ рдЕрдирд╛рд╡рд╢реНрдпрдХ рдХреЛрдб рдХрд╛ рдЙрддреНрдкрд╛рджрди рди рдХрд░реЗрдВ рдФрд░ рдпрд╣рд╛рдВ рдПрдХ рдЕрдирд╛рд╡рд╢реНрдпрдХ рдЪрдХреНрд░реАрдп рдирд┐рд░реНрднрд░рддрд╛ рдХрд╛ рдкрд░рд┐рдЪрдп рди рджреЗрдВ, рд╣рд╛рд▓рд╛рдВрдХрд┐ рдпрд╣ "рд╣рд▓" рд╣реЛ рд╕рдХрддрд╛ рд╣реИред рдЗрд╕ рдкреНрд░рдХрд╛рд░, рдХреЗрд╡рд▓ .isOpen рд╕рдВрдкрддреНрддрд┐ рдХреЛ рд╕реАрдзреЗ рдмрджрд▓рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИред

рд╕рд╣реА рдкреНрд░рджрд░реНрд╢рди рдХреЗ рд▓рд┐рдП, рд╣рдорд╛рд░реЗ рд▓рд┐рдП рдпрд╣ рдЬрд╛рдирдирд╛ рднреА рдорд╣рддреНрд╡рдкреВрд░реНрдг рд╣реИ рдХрд┐ рдХреНрдпрд╛ рдиреЛрдб рдореЗрдВ рдмрдЪреНрдЪреЗ рд╣реИрдВ (рдХреНрдпрд╛ рдЗрд╕реЗ рдЦреЛрд▓рд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ) рдФрд░ рдХреНрдпрд╛ рдиреЛрдб рдЕрдкрдиреЗ рд╕реНрддрд░ рдкрд░ рдЕрдВрддрд┐рдо рд╣реИ, рддрд╛рдХрд┐ рдЕрдЧрд▓реЗ рдиреЛрдб рд╕реЗ рдПрдХ рд░реЗрдЦрд╛ рди рдЦреАрдВрдЪреА рдЬрд╛ рд╕рдХреЗред
 function TreeViewNode(caption,children){ ... this.children = children||[]; this.isLeaf = !this.children.length; this.isLast = false; ... } 

рдЪреВрдВрдХрд┐ рдиреЛрдб рдореЗрдВ рдмрдЪреНрдЪреЗ рдирд╣реАрдВ рд╣реЛ рд╕рдХрддреЗ рд╣реИрдВ, рдЬрд┐рд╕рдХрд╛ рдЕрд░реНрде рд╣реИ рдХрд┐ children рд╕рдВрдкрддреНрддрд┐ рдпрд╛ рддреЛ рдЦрд╛рд▓реА (рдЕрд╢рдХреНрдд рдпрд╛ рдЕрдкрд░рд┐рднрд╛рд╖рд┐рдд) рд╣реИ, рдпрд╛ рд╢реВрдиреНрдп рд▓рдВрдмрд╛рдИ рдХреА рдПрдХ рд╕рд░рдгреА рд╣реИ, рдореИрдВрдиреЗ рдЕрд╕рдВрджрд┐рдЧреНрдзрддрд╛ рдХреЗ рдЗрд╕ рд╕рд╡рд╛рд▓ рдХреЛ рдЬреЛрдбрд╝рд╛ - рдпрджрд┐ рдмрдЪреНрдЪреЛрдВ рдХреЛ рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рдирд╣реАрдВ рдХрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛, рддреЛ рдЦрд╛рд▓реА рд╕рд░рдгреА рдХреЗ рд╕рд╛рде рд╕рдВрдкрддреНрддрд┐ рдХреЛ рдЖрд░рдореНрдн рдХрд┐рдпрд╛ред

рд╕рдореНтАНрдорд┐рд▓рд┐рдд рд╕рдВрдкрддреНрддрд┐ рдХреЗ рд▓рд┐рдП, рдЗрд╕рдХреЗ рдХреНрд░рд┐рдпрд╛рдиреНрд╡рдпрди рдХреЗ рд▓рд┐рдП рджреЛ рд╡рд┐рдХрд▓реНрдк рд╣реИрдВред рдпрд╣ рдЕрдкрдиреЗ рдорд╛рддрд╛-рдкрд┐рддрд╛ рдХреЗ рдиреЛрдб рдХреЗ рд▓рд┐рдП рджрд┐рдП рдЬрд╛ рд░рд╣реЗ рд▓рд┐рдВрдХ рдХреЗ рд╕рд╛рде ko.computed рдмрдирд╛рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рдпрд╛ рдорд╛рддрд╛-рдкрд┐рддрд╛ рдЕрдкрдиреЗ рдмрдЪреНрдЪреЛрдВ рдХреА рд╕рдореНтАНрдорд┐рд▓рд┐рдд рд╕рдВрдкрддреНрддрд┐рдпреЛрдВ рдХреА рдЧрдгрдирд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рдореИрдВрдиреЗ рджреВрд╕рд░рд╛ рддрд░реАрдХрд╛ рдЪреБрдирд╛ рдХреНрдпреЛрдВрдХрд┐ рдЗрд╕рдХреЗ рд╕рд╛рде рдХрдо рдХреЛрдб рд╣реИред рдкрд╣рд▓рд╛ рджреГрд╖реНрдЯрд┐рдХреЛрдг рднреА рдХрд╛рдо рдЖ рд╕рдХрддрд╛ рд╣реИ, рдХреНрдпреЛрдВрдХрд┐ рдПрдХ рдиреЛрдб рдХреЗ рдорд╛рддрд╛-рдкрд┐рддрд╛ рдХреЗ рд╕рдВрджрд░реНрдн рдореЗрдВ рдХрдИ рдкрд░рд┐рджреГрд╢реНрдпреЛрдВ рдореЗрдВ рдЙрдкрдпреЛрдЧреА рд╣реИред рд╣рд╛рд▓рд╛рдВрдХрд┐, рдПрдХ рд╕реЗ рджреВрд╕рд░реЗ рдореЗрдВ рдЬрд╛рдирд╛ рдлрд┐рд░ рдЖрд╕рд╛рди рд╣реЛрдЧрд╛ред
рдЗрд╕рд▓рд┐рдП, рд╣рдо рд╕рдореНтАНрдорд┐рд▓рд┐рдд рд╕рдВрдкрддреНрддрд┐ рдХрд╛ рд╕рдВрд╕рд╛рдзрди рдЬреЛрдбрд╝рддреЗ рд╣реИрдВ:

 function setIsLast(children){ for(var i=0,l=children.length;i<l;i++){ children[i].isLast = (i==(l-1)); } } function TreeViewNode(caption,children){ ... setIsLast(this.children); ... } function TreeView(children){ ... setIsLast(this.children); ... } 


рдЕрдм рдЯреЗрдореНрдкрд▓реЗрдЯ рдореЗрдВ рд╕рдВрдмрдВрдзрд┐рдд рд╡рд░реНрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рдмрд╛рдзреНрдпрдХрд╛рд░реА рдиреЛрдб рдЯреЗрдореНрдкрд▓реЗрдЯ рдЬреЛрдбрд╝реЗрдВ:
 ... <li data-bind='css:{closed:isClosed,open:isOpen, leaf: isLeaf, last: isLast}'> ... </li> ... 


рдпрд╣ рдиреЛрдбреНрд╕ рдХреЛ рдврд╣рд╛рдиреЗ рдФрд░ рд╡рд┐рд╕реНрддрд╛рд░рд┐рдд рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдмрдиреА рд╣реБрдИ рд╣реИ:
 function TreeViewNode(caption,children){ var self = this; ... this.toggleOpen = function(){ self.isOpen(!self.isOpen()); }; ... } 

рдФрд░ рдмрдВрдзрди рдЬреЛрдбрд╝реЗрдВ:
 ... <li data-bind='css:{closed:isClosed,open:isOpen, leaf: isLeaf, last: isLast}, click: toggleOpen, clickBubble: false'> ... </li> ... 

clickBubble: false рдЖрд╡рд╢реНрдпрдХ рд╣реИ рддрд╛рдХрд┐ рдШрдЯрдирд╛ рдорд╛рддрд╛-рдкрд┐рддрд╛ рдХреЗ рд▓рд┐рдП рдкреЙрдк рди рд╣реЛ рдФрд░ рдЙрдиреНрд╣реЗрдВ рдкреНрд░рднрд╛рд╡рд┐рдд рди рдХрд░реЗред

рдУрдк-рдкрд╛ рдирдИ рд╢реИрд▓реАред


рдЖрдк CSS рдкрд░ рдЬрд╛ рд╕рдХрддреЗ рд╣реИрдВред рдореИрдВ рдЕрдкрдиреЗ рд╕реАрдПрд╕рдПрд╕ рдХреЗ рд╕рд╛рде рдирд╣реАрдВ рдЖрдпрд╛, рд▓реЗрдХрд┐рди рдмрд╕ рдПрдХ рдЕрдиреНрдп рдкреБрд╕реНрддрдХрд╛рд▓рдп рд╕реЗ рд╢реИрд▓рд┐рдпреЛрдВ рдХреЛ рд╕рд░рд▓ рдХрд┐рдпрд╛ред

рдореИрдВ рдЙрдиреНрд╣реЗрдВ рдиреАрдЪреЗ рдмреЛрд▓реА:
рд╢реИрд▓рд┐рдпреЛрдВ
 .tree li, .tree ins{ background-image:url("http://habrastorage.org/storage2/0eb/507/98d/0eb50798dca00f5cc8e153e6da9a87f9.png"); background-repeat:no-repeat; background-color:transparent; } .tree li { background-position:-90px 0; background-repeat:repeat-y; } .tree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } .tree ul, .tree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } .tree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } .tree > ul > li { margin-left:0px; } .tree li.last { background:transparent; } .tree .open > ins { background-position:-72px 0;} .tree .closed > ins { background-position:-54px 0;} .tree .leaf > ins { background-position:-36px 0;} .tree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } li.open > ul { display:block; } li.closed > ul { display:none; } 



рдкреВрд░реНрдг рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ
 function setIsLast(children){ for(var i=0,l=children.length;i<l;i++){ children[i].isLast = (i==(l-1)); } } function TreeViewNode(caption,children){ var self = this; this.caption = caption; this.children = children||[]; this.isOpen = ko.observable(); this.isClosed = ko.computed(function(){ return !this.isOpen(); },this); this.isLeaf = !this.children.length; this.isLast = false; setIsLast(this.children); this.toggleOpen = function(){ self.isOpen(!self.isOpen()); }; } function TreeView(children){ this.children = children; setIsLast(this.children); } var vm = { tree: new TreeView([ new TreeViewNode('Node 1',[ new TreeViewNode('Node 3') ]), new TreeViewNode('Node 2') ]) }; ko.applyBindings(vm); 



рдкреВрд░реНрдг HTML
 <div class='tree' data-bind='with: tree'> <ul data-bind='template: {name:"treeNode", foreach: children}'> </ul> </div> <script id='treeNode' type='text/html'> <li data-bind='css:{closed:isClosed,open:isOpen, leaf: isLeaf, last: isLast}, click: toggleOpen, clickBubble: false'> <ins></ins> <span data-bind='text:caption'></span> <ul data-bind='template: {name:"treeNode", foreach: children}'> </ul> </li> </script> 



рдпрд╣ рдХреБрдЫ рдЗрд╕ рддрд░рд╣ рд╕реЗ рдирд┐рдХрд▓рд╛:

рдкреНрд░рдпреЛрдЧ рдХреЗ рд▓рд┐рдП JSFiddle ред

рдмрд╣реБрдд рд╕реБрдВрджрд░ рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдХрд╛рдлреА рдХрд╛рд░реНрдпрд╛рддреНрдордХ рд╣реИред рдЕрдм рдкреНрд░рддреНрдпреЗрдХ рдиреЛрдб рдореЗрдВ рдЖрдЗрдХрди рдпрд╛ рдЪреЗрдХрдмреЙрдХреНрд╕ рдЬреЛрдбрд╝рдирд╛ рдореБрд╢реНрдХрд┐рд▓ рдирд╣реАрдВ рд╣реИред рдЖрдк рдиреЛрдбреНрд╕ рдХреЛ рдмрд╛рд╣рд░ рдЦрдбрд╝рд╛ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред рд▓реЗрдХрд┐рди рд╕рдмрд╕реЗ рдкрд╣рд▓реЗ, рдореИрдВ рдХреЛрдб рдХреЛ рдЕрдзрд┐рдХ рд╕рд╛рд░реНрд╡рднреМрдорд┐рдХ рдмрдирд╛рдирд╛ рдЪрд╛рд╣реВрдВрдЧрд╛ рддрд╛рдХрд┐ рдЯреНрд░реАрд╡реНрдпреВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдЖрд╕рд╛рдиреА рд╕реЗ рдПрдХ рдордирдорд╛рдирд╛ рд╢реНрд░реЗрдгреАрдмрджреНрдз рдбреЗрдЯрд╛ рд╕рдВрд░рдЪрдирд╛ рдХреЗ рд╕рд╛рде рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХреЗред рдЗрд╕рдХреЗ рдЕрд▓рд╛рд╡рд╛, рдпрд╣ рдЖрд╡рд╢реНрдпрдХ рд╣реИ рдХрд┐ рдбреЗрдЯрд╛ рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрди рдЯреНрд░реА рдореЙрдбрд▓ рдореЗрдВ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдкрд░рд┐рд▓рдХреНрд╖рд┐рдд рд╣реЛрдВ, рдФрд░ рдЖрдЧреЗ DOM рдореЗрдВред

рдПрдлрдкреА рдореЗрдВ, рдЗрд╕реЗ "рдорд╛рдирдЪрд┐рддреНрд░" рдХрд╣рд╛ рдЬрд╛рддрд╛ рд╣реИред


рдЗрд╕рдХреЗ рд╕рд╛рде рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдо рдПрдХ рд╕рд╣рд╛рдпрдХ рдлрд╝рдВрдХреНрд╢рди рдХрд╛ рдкрд░рд┐рдЪрдп рджреЗрддреЗ рд╣реИрдВ рдЬреЛ рдбреЗрдЯрд╛ рд╕рд░рдгреА рдореЗрдВ рдиреЛрдб рдореЙрдбрд▓ рдХреА рдПрдХ рд╕рд░рдгреА рдкреНрд░рджрд╛рди рдХрд░реЗрдЧрд╛ред
 function dataToNodes(dataArray){ var res = []; for(var i=0,l=dataArray.length;i<l;i++){ res.push(new TreeViewNode(dataArray[i])); } return res; } 

TreeViewNode рдЕрдм рдЗрдирдкреБрдЯ рдкрд░ рдиреЛрдб рдФрд░ "рдмрдЪреНрдЪреЛрдВ" рдХрд╛ рд▓реЗрдмрд▓ рдирд╣реАрдВ рд▓реЗрддрд╛ рд╣реИ, рд▓реЗрдХрд┐рди рдХреБрдЫ рд╕рд╛рд░ рдбреЗрдЯрд╛ред рдЬрд╛рд╣рд┐рд░ рд╣реИ, рд╢рд┐рд▓рд╛рд▓реЗрдЦ рдФрд░ "рдмрдЪреНрдЪреЗ" рдЙрд╕реЗ рдбреЗрдЯрд╛ рд╕реЗ рдирд┐рдХрд╛рд▓рдирд╛ рд╣реЛрдЧрд╛ред рдФрд░ рд╢реБрд░реВ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдорд╛рди рд▓реЗрдВ рдХрд┐ рдбреЗрдЯрд╛ рдкреВрд░реА рддрд░рд╣ рд╕реЗ рд╕рд╛рд░ рдирд╣реАрдВ рд╣реИ, рд▓реЗрдХрд┐рди рдпрд╣ рдПрдХ рдРрд╕реА рд╡рд╕реНрддреБ рд╣реИ рдЬрд┐рд╕рдореЗрдВ рд╣рд╕реНрддрд╛рдХреНрд╖рд░ рдХреЛ caption рд╕рдВрдкрддреНрддрд┐ рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИ, рдФрд░ children рд╕рдВрдкрддреНрддрд┐ рдореЗрдВ "рдмрдЪреНрдЪреЗ", рдЬреЛ рдПрдХ рд╕рд░рдгреА рд╣реИред

 function TreeViewNode(data){ ... this.data = data; this.caption = data.caption; if(data.children) this.children = dataToNodes(data.children); else this.children = []; ... } 


рдпрд╣ рд╡рд╣ рдорд╛рдирдЪрд┐рддреНрд░ рдирд╣реАрдВ рд╣реИ рдЬреЛ FP рдореЗрдВ рдореИрдк рдХрд░рддрд╛ рд╣реИред


рд╣рд╛рд▓рд╛рдВрдХрд┐, рд╕рдВрдкрддреНрддрд┐ рдХреЗ рдирд╛рдореЛрдВ рдХреЗ рд▓рд┐рдП рддрдВрдЧ рдмрдВрдзрди рдХреЛ рдЫреЛрдбрд╝рдирд╛ рд╣рдорд╛рд░реЗ рд▓рд┐рдП рдЖрд╕рд╛рди рд╣реИред рдХреБрдЫ map рдСрдмреНрдЬреЗрдХреНрдЯ рд╣рдореЗрдВ рдкрд╛рд╕ рдХрд░рдиреЗ рджреЗрдВ, рдЬрд┐рд╕рдореЗрдВ рдЧреБрдгреЛрдВ рдХреЗ рдкрддреНрд░рд╛рдЪрд╛рд░ рдХрд╛ рд╕рдВрдХреЗрдд рджрд┐рдпрд╛ рдЧрдпрд╛ рд╣реИ:

 function TreeViewNode(data){ ... this.data = data; var captionProp = (map && map.caption)||'caption', childrenProp = (map && map.children)||'children'; this.caption = data[captionProp]; if(data[childrenProp]) this.children = dataToNodes(data[childrenProp]); else this.children = []; ... } 


рдФрд░ рдЗрд╕рд╕реЗ рднреА рдмреЗрд╣рддрд░, data рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЗ рдкреНрд░рдХрд╛рд░ рдХреЗ рдЖрдзрд╛рд░ рдкрд░, рдЧреБрдгреЛрдВ рдХреЗ рдкрддреНрд░рд╛рдЪрд╛рд░ рдХреЛ рдЧрддрд┐рд╢реАрд▓ рд░реВрдк рд╕реЗ рдирд┐рд░реНрдзрд╛рд░рд┐рдд рдХрд░рдиреЗ рдореЗрдВ рд╕рдХреНрд╖рдо рд╣реЛрдирд╛:
 function TreeViewNode(data){ ... this.data = data; var map = (typeof propMap == 'function') ? propMap(data):propMap, captionProp = (map && map.caption)||'caption', childrenProp = (map && map.children)||'children'; ... } 


рдЕрдм рдЖрдк TreeViewNode рдШреЛрд╖рдгрд╛ рдФрд░ рд╕рд╣рд╛рдпрдХ рдХрд╛рд░реНрдпреЛрдВ рдХреЛ TreeView рдореЙрдбрд▓ рдШреЛрд╖рдгрд╛ рдХреЗ рдЕрдВрджрд░ рдЫрд┐рдкрд╛ рд╕рдХрддреЗ рд╣реИрдВ, рдХреНрдпреЛрдВрдХрд┐ TreeViewNode рдЙрджрд╛рд╣рд░рдг рдЕрдм рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рджреНрд╡рд╛рд░рд╛ рд╕реНрд╡рддрдВрддреНрд░ рд░реВрдк рд╕реЗ рдирд╣реАрдВ рдмрдирд╛рдП рдЬрд╛рдиреЗ рдЪрд╛рд╣рд┐рдПред
рдкреВрд░реНрдг рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ
 function TreeView(data, propMap){ this.data = data; this.children = dataToNodes(data); setIsLast(this.children); function dataToNodes(dataArray){ var res = []; for(var i=0,l=dataArray.length;i<l;i++){ res.push(new TreeViewNode(dataArray[i])); } return res; } function setIsLast(children){ for(var i=0,l=children.length;i<l;i++){ children[i].isLast = (i==(l-1)); } } function TreeViewNode(data){ var self = this; this.data = data; var map = (typeof propMap == 'function') ? propMap(data):propMap; captionProp = (map && map.caption)||'caption', childrenProp = (map && map.children)||'children'; this.caption = data[captionProp]; if(data[childrenProp]) this.children = dataToNodes(data[childrenProp]); else this.children = []; this.isOpen = ko.observable(); this.isClosed = ko.computed(function(){ return !this.isOpen(); },this); this.isLeaf = !this.children.length; this.isLast = false; setIsLast(this.children); this.toggleOpen = function(){ self.isOpen(!self.isOpen()); }; } } var vm = { data: [ { name:'Node 1', list: [{ name: 'Node 3' }] }, { name:'Node2', list: [{ name: 'Node 6', list: [{ name: 'Node 5' }] }] } ] }; vm.tree = new TreeView(vm.data,{caption:'name',children:'list'}); ko.applyBindings(vm); 


рдкреНрд░рдпреЛрдЧ рдХреЗ рд▓рд┐рдП JSFiddle ред

рдпрд╣рд╛рдВ рд╕реЗ рдЬрд╛рджреВ рд╢реБрд░реВ рд╣реЛрддрд╛ рд╣реИред


рдЕрдм рд╣рдореЗрдВ рдЕрдВрддрд┐рдо рдЖрд╡рд╢реНрдпрдХрддрд╛ рдХреЛ рдкреВрд░рд╛ рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ - "рдкреЗрдбрд╝" рдореЙрдбрд▓ рдореЗрдВ рдбреЗрдЯрд╛ рдкрд░рд┐рд╡рд░реНрддрди рдХрд╛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдкреНрд░рддрд┐рдмрд┐рдВрдмред рд╣рдо ko.observable, ko.computed, ko.observableArray рд╡рд╕реНрддреБрдУрдВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ KnockoutJS рдХреЗ "рдЬрд╛рджреВ" рдХрд╛ рдЙрдкрдпреЛрдЧ ko.observable, ko.computed, ko.observableArray ред рдРрд╕рд╛ рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рд╣рдореЗрдВ рд╕рд┐рд░реНрдл children рд╕рдВрдкрддреНрддрд┐ рдХреЛ рд╕рдВрдЧрдгрдХ рдмрдирд╛рдиреЗ рдХреА рдЬрд░реВрд░рдд рд╣реИред рдФрд░ рдЙрд╕ рдкрд░ рдирд┐рд░реНрднрд░ рдЕрдиреНрдп рд╕рдВрдкрддреНрддрд┐рдпреЛрдВ рдХреЗ рд▓рд┐рдП рдХреЛрдб рднреА рдмрджрд▓реЗрдВ:
  function TreeViewNode(data){ ... if(data[childrenProp]) this.children = ko.computed(function(){ return dataToNodes(ko.utils.unwrapObservable(data[childrenProp])); }); else this.children = null; ... this.isLeaf = ko.computed(function(){ return !(this.children && this.children().length); },this); this.isLast = ko.observable(false); if(this.children){ setIsLast(this.children()); this.children.subscribe(function(newVal){ setIsLast(newVal); }); } ... 

ko.utils.unwrapObservable рдлрд╝рдВрдХреНрд╢рди рдЕрд╡рд▓реЛрдХрди рдХрд┐рдП рдЧрдП рдСрдмреНрдЬреЗрдХреНрдЯ рдХрд╛ рд╡рд░реНрддрдорд╛рди рдорд╛рди рд▓реМрдЯрд╛рддрд╛ рд╣реИ, рдпрд╛, рдпрджрд┐ рдпрд╣ рдПрдХ рдСрдмреНрдЬреЗрдХреНрдЯрд┐рд╡ рдСрдмреНрдЬреЗрдХреНрдЯ рдирд╣реАрдВ рд╣реИ, рддреЛ рд╡рд╣реА рдорд╛рди рдЬреЛ рдЗрд╕реЗ рдЗрдирдкреБрдЯ рдХреЗ рд░реВрдк рдореЗрдВ рджрд┐рдпрд╛ рдЧрдпрд╛ рдерд╛ред ko.utils.unwrapObservable рдЕрдВрджрд░ ko.computed рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдПрдХ рдирд┐рд░реНрднрд░рддрд╛ рдкреИрджрд╛ рдХрд░реЗрдЧрд╛ рдФрд░ рдбреЗрдЯрд╛ рдХреЗ рд░реВрдк рдореЗрдВ рдЕрд╡рд▓реЛрдХрди рдореВрд▓реНрдп рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдиреЗ рдкрд░ .children рдЕрдкрдиреЗ рдЖрдк рдЕрдкрдбреЗрдЯ рд╣реЛ рдЬрд╛рдПрдЧрд╛ред рджреВрд╕рд░реА рдУрд░, рдЖрдк рдмрд╕ рдПрдХ рдЬреЗрдПрд╕ рд╕рд░рдгреА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ, рдлрд┐рд░ рдХреЛрдИ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рдкрд░рд┐рд╡рд░реНрддрди рдЯреНрд░реИрдХрд┐рдВрдЧ рдирд╣реАрдВ рд╣реЛрдЧреАред
рд╣рдо TreeView рд▓рд┐рдП рднреА рдРрд╕рд╛ рд╣реА рдХрд░рддреЗ рд╣реИрдВ
 function TreeView(data, propMap){ ... this.children = ko.computed(function(){ return dataToNodes(ko.utils.unwrapObservable(data)); }); setIsLast(this.children()); this.children.subscribe(function(newVal){ setIsLast(newVal); }); ... 

рдЕрдм рдбреЗрдЯрд╛ рдореЗрдВ рдкрд░рд┐рд╡рд░реНрддрди рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ "рдЯреНрд░реА" рдХреЗ рдореЙрдбрд▓ рдореЗрдВ рдФрд░ рдлрд┐рд░ рдбреЛрдо рдореЗрдВ рд╕реНрд╡рдЪрд╛рд▓рд┐рдд рд░реВрдк рд╕реЗ рдкрд░рд┐рд▓рдХреНрд╖рд┐рдд рд╣реЛрдЧрд╛ред
рдЖрдк JSFiddle рдХреЗ рд╕рд╛рде рдкреНрд░рдпреЛрдЧ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
рдХреЗрд╡рд▓ рдПрдХ рдЕрдкреНрд░рд┐рдп рд╕рдорд╕реНрдпрд╛ рд╣реИ - рдиреЛрдбреНрд╕ рдЬреЛрдбрд╝рдиреЗ рд╕реЗ рд╣рдорд╛рд░рд╛ рдкреЗрдбрд╝ рдЧрд┐рд░ рдЬрд╛рддрд╛ рд╣реИред рдРрд╕рд╛ рдЗрд╕рд▓рд┐рдП рд╣реИ рдХреНрдпреЛрдВрдХрд┐ рд╣рд░ рдмрд╛рд░ рдЬрдм рд╣рдо рдЕрдкрдбреЗрдЯ рдХрд░рддреЗ рд╣реИрдВ, рд╣рдо TreeViewNode - TreeViewNode рдореЙрдбрд▓ рдХреЛ рдлрд┐рд░ рд╕реЗ рдмрдирд╛рддреЗ рд╣реИрдВред рдПрдХ рдЪрддреБрд░ рджреГрд╖реНрдЯрд┐рдХреЛрдг рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ - рдХреЗрд╡рд▓ рдирдП рдбреЗрдЯрд╛ рдХреЗ рд▓рд┐рдП рдореЙрдбрд▓ рдмрдирд╛рдиреЗ рдХреЗ рд▓рд┐рдП, рдФрд░ рдкреБрд░рд╛рдиреЗ рдЙрдкрдпреЛрдЧ рдХреЗ рд▓рд┐рдП рдкреБрд░рд╛рдиреЗред рдЗрд╕реЗ рджреЛ рддрд░реАрдХреЛрдВ рд╕реЗ рд▓рд╛рдЧреВ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ:
  1. рдбреЗрдЯрд╛ рдореЗрдВ TreeViewNode рд▓рд┐рдВрдХ рд░рдЦреЗрдВ;
  2. рдиреЛрдбреНрд╕ рдХреА рд╕реВрдЪреА рдХреЛ рдЕрджреНрдпрддрди рдХрд░рддреЗ рд╕рдордп, рдкреБрд░рд╛рдиреА рд╕реВрдЪреА рдореЗрдВ TreeViewNode ред

рдореИрдВ рдкрд╣рд▓реА рд╡рд┐рдзрд┐ рджрд┐рдЦрд╛рдКрдВрдЧрд╛, рдХреНрдпреЛрдВрдХрд┐ рдпрд╣ рдЫреЛрдЯреА рд╣реИред рд╣рд╛рд▓рд╛рдВрдХрд┐, рдЗрд╕рдХреА рдПрдХ рд╕реАрдорд╛ рд╣реИ - рдпрджрд┐ рдЖрдк рдкреЗрдбрд╝ рдХреЗ рд╡рд┐рднрд┐рдиреНрди рдиреЛрдбреНрд╕ рдХреЗ рд▓рд┐рдП рдПрдХ рд╣реА рд╡рд╕реНрддреБ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдирд╛ рдЪрд╛рд╣рддреЗ рд╣реИрдВ, рддреЛ рдпрд╣ рд╡рд┐рдзрд┐ рдХрд╛рдо рдирд╣реАрдВ рдХрд░реЗрдЧреАред рдЕрдзрд┐рдХ рд╕рдЯреАрдХ рд░реВрдк рд╕реЗ, рдпрд╣ рдЕрдкреНрд░рддреНрдпрд╛рд╢рд┐рдд рдкреНрд░рднрд╛рд╡ рдХреЛ рдЬрдиреНрдо рджреЗрдЧрд╛ред рд▓реЗрдХрд┐рди рдЕрдЧрд░ рдЖрдкрдХреЗ рдкрд╛рд╕ рдкреНрд░рддреНрдпреЗрдХ рдбреЗрдЯрд╛ рдСрдмреНрдЬреЗрдХреНрдЯ рдХреЗ рдЕрдиреБрд░реВрдк рдХреЗрд╡рд▓ рдПрдХ рдЯреНрд░реА рдиреЛрдб рд╣реИ, рддреЛ рд╕рдм рдХреБрдЫ рдареАрдХ рд╣реЛрдЧрд╛ред
рддреЛ:
  function TreeViewNode(data){ ... data._treeviewNode = this; //        ... } function dataToNodes(dataArray,old){ var res = []; for(var i=0,l=dataArray.length;i<l;i++){ res.push(dataArray[i]._treeviewNode || new TreeViewNode(dataArray[i])); //        } return res; } 


рдкрд╕рдВрдж рд╣рдореЗрд╢рд╛ рдПрдХ рдЦреБрд╢реА рд╣реИред


рд╣рдорд╛рд░рд╛ "рдкреЗрдбрд╝" рд╡реНрдпрд╛рд╡рд╣рд╛рд░рд┐рдХ рд░реВрдк рд╕реЗ рдЙрдЧрд╛рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред рдкреВрд░реНрдг рдкреНрд░рд╕рдиреНрдирддрд╛ рдХреЗ рд▓рд┐рдП, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ "рдкреЗрдбрд╝" рдХреЗ рдЕрд▓рдЧ-рдЕрд▓рдЧ рдиреЛрдбреНрд╕ рдХрд╛ рдЪрдпрди рдХрд░рдиреЗ рдХреА рдХреНрд╖рдорддрд╛ рдирд╣реАрдВ рд╣реИред
 function TreeView(data, propMap){ var treeView = this; //    TreeView this.selectedNode = ko.observable(); //   ... function TreeViewNode(data){ ... this.isSelected = ko.computed(function(){ //      return (treeView.selectedNode()===this) },this); this.toggleSelection = function(){ //     if(this.isSelected()) treeView.selectedNode(null); else treeView.selectedNode(this); } } } 

рдЯреЗрдореНрдкрд▓реЗрдЯ рдХреЛ рдкреВрд░рдХ рдХрд░рдирд╛ рднреА рдЖрд╡рд╢реНрдпрдХ рд╣реИ:
 ... <span class='caption' data-bind='text:caption, css: {selected:isSelected},click:toggleSelection, clickBubble: false'></span> ... 


рдЕрдм рд╣рдо "рдкреЗрдбрд╝" рдХреЗ рдкреВрд░реНрдг рд╡рд┐рдХрд╕рд┐рдд рд╕рдВрдкрд╛рджрдХ рдХрд╛ рдирд┐рд░реНрдорд╛рдг рдХрд░ рд╕рдХрддреЗ рд╣реИрдВред
рдкреВрд░реНрдг рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ
 function TreeView(data, propMap){ var treeView = this; this.data = data; this.selectedNode = ko.observable(); this.children = ko.computed(function(){ return dataToNodes(ko.utils.unwrapObservable(data)); }); setIsLast(this.children()); this.children.subscribe(function(newVal){ setIsLast(newVal); }); function dataToNodes(dataArray,old){ var res = []; for(var i=0,l=dataArray.length;i<l;i++){ res.push(dataArray[i]._treeviewNode || new TreeViewNode(dataArray[i])); } return res; } function setIsLast(children){ for(var i=0,l=children.length;i<l;i++){ children[i].isLast(i==(l-1)); } } function TreeViewNode(data){ var self = this; this.data = data; data._treeviewNode = this; var map = (typeof propMap == 'function') ? propMap(data):propMap; captionProp = (map && map.caption)||'caption', childrenProp = (map && map.children)||'children'; this.caption = data[captionProp]; if(data[childrenProp]) this.children = ko.computed(function(){ return dataToNodes(ko.utils.unwrapObservable(data[childrenProp])); }); else this.children = null; this.isOpen = ko.observable(); this.isClosed = ko.computed(function(){ return !this.isOpen(); },this); this.isLeaf = ko.computed(function(){ return !(this.children && this.children().length); },this); this.isLast = ko.observable(false); if(this.children){ setIsLast(this.children()); this.children.subscribe(function(newVal){ setIsLast(newVal); }); } this.toggleOpen = function(){ self.isOpen(!self.isOpen()); }; this.isSelected = ko.computed(function(){ return (treeView.selectedNode()===this) },this); this.toggleSelection = function(){ if(this.isSelected()) treeView.selectedNode(null); else treeView.selectedNode(this); } } } function SomeObject(col){ this.name = ko.observable('New SomeObject'); this.list = ko.observableArray(); this.collection = col; } var vm = { data:ko.observableArray(), AddRootNode: function(){ this.data.push(new SomeObject(this.data)); }, AddChildNode: function(){ var data = this.tree.selectedNode().data; data.list.push(new SomeObject(data.list)); }, RemoveNode:function(){ var data = this.tree.selectedNode().data; this.tree.selectedNode(null); data.collection.remove(data); } }; vm.tree = new TreeView(vm.data,{caption:'name',children:'list'}); ko.applyBindings(vm); 


рдкреВрд░реНрдг HTML
 <button data-bind='click:AddRootNode'>Add New Root Node</button> <button data-bind='click:AddChildNode,visible: tree.selectedNode'>Add Child toSelected Node</button> <button data-bind='click:RemoveNode,visible: tree.selectedNode'>Delete Selected Node</button> <div class='tree' data-bind='with: tree'> <ul data-bind='template: {name:"treeNode", foreach: children}'> </ul> </div> <div data-bind='with: tree.selectedNode'> <!-- ko with: data --> <input data-bind='value:name'> <!-- /ko --> </div> <script id='treeNode' type='text/html'> <li data-bind='css:{closed:isClosed,open:isOpen, leaf: isLeaf, last: isLast}, click: toggleOpen, clickBubble: false'> <ins></ins> <span class='caption' data-bind='text:caption, css: {selected:isSelected},click:toggleSelection, clickBubble: false'></span> <ul data-bind='template: {name:"treeNode", foreach: children}'> </ul> </li> </script> 


рдкреВрд░реНрдг рд╕реАрдПрд╕рдПрд╕
 .tree li, .tree ins{ background-image:url("http://habrastorage.org/storage2/0eb/507/98d/0eb50798dca00f5cc8e153e6da9a87f9.png"); background-repeat:no-repeat; background-color:transparent; } .tree li { background-position:-90px 0; background-repeat:repeat-y; } .tree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } .tree ul, .tree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } .tree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } .tree > ul > li { margin-left:0px; } .tree li.last { background:transparent; } .tree .open > ins { background-position:-72px 0;} .tree .closed > ins { background-position:-54px 0;} .tree .leaf > ins { background-position:-36px 0;} .tree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } li.open > ul { display:block; } li.closed > ul { display:none; } .selected {background-color: #ccc; } span.caption {cursor: pointer} 



рдкреНрд░рдпреЛрдЧ рдХреЗ рд▓рд┐рдП JSFiddle ред

рдкрд░рд┐рдгрд╛рдоред рдЫрд┐рдкреЗ рд╣реБрдП рдкреАрдЖрд░ред рд╣рд╛рдерд┐рдпреЛрдВ рдХрд╛ рд╡рд┐рддрд░рдгред


рддреЛ, рд╣рдореЗрдВ рдиреЙрдХрдЖрдЙрдЯ рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдЕрдиреБрдХреВрд▓рд┐рдд рдПрдХ рдкреВрд░реА рддрд░рд╣ рдХрд╛рд░реНрдпрд╛рддреНрдордХ рдЯреНрд░реА рд╡реНрдпреВ рдХрд╛рд░реНрдпрд╛рдиреНрд╡рдпрди рдорд┐рд▓рд╛, рдЬреЛ рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдореЗрдВ рдХреЛрдб рдХреА рд╕рд┐рд░реНрдл 60 рд╕реЗ рдЕрдзрд┐рдХ рд▓рд╛рдЗрдиреЗрдВ рд▓реЗрддрд╛ рд╣реИ, рд╕рдордЭ рдореЗрдВ рдЖрддрд╛ рд╣реИ, рдЖрд╕рд╛рдиреА рд╕реЗ рдирдП рдХрд╛рд░реНрдпреЛрдВ рдХреЗ рд╕рд╛рде рд╡рд┐рд╕реНрддрд╛рд░ рдХрд┐рдпрд╛ рдЬрд╛ рд╕рдХрддрд╛ рд╣реИ, рдФрд░ рдЖрд╕рд╛рдиреА рд╕реЗ рдбреЗрдЯрд╛ рдореЙрдбрд▓ рдХреЗ рд▓рд┐рдП рдЕрдиреБрдХреВрд▓ рд╣реЛрддрд╛ рд╣реИред рдЕрдм рдкреБрди: рдЙрдкрдпреЛрдЧ рдХреЗ рд╕рдВрднрд╛рд╡рд┐рдд рдкрд░рд┐рджреГрд╢реНрдпреЛрдВ рдкрд░ рд╡рд┐рдЪрд╛рд░ рдХрд░реЗрдВ:
  1. рдХреЙрдкреА рдФрд░ рдкреЗрд╕реНрдЯ рдХрд░реЗрдВ рдХрд╛рд░реНрдп рдХрд░реЗрдВ рдЕрдкрдиреЗ рдЬреЗрдПрд╕ рдХреЛрдб рдХреЛ рдЯреНрд░реА рд╡реНрдпреВ рдХрд░реЗрдВ рдпрд╛ рдЗрд╕реЗ рдПрдХ рдЕрд▓рдЧ рдлрд╛рдЗрд▓ рдореЗрдВ рдбрд╛рд▓реЗрдВред рдЕрдкрдиреА рд╢реИрд▓рд┐рдпреЛрдВ рдореЗрдВ рд╕реНрдЯрд╛рдЗрд▓ рдбрд╛рд▓реЗрдВ, рдпрд╛ рдПрдХ рдЕрд▓рдЧ рдлрд╝рд╛рдЗрд▓ рдХреЗ рд░реВрдк рдореЗрдВ рд╢реИрд▓рд┐рдпреЛрдВ рдХреЛ рдЖрдпрд╛рдд рдХрд░реЗрдВред рдЯреЗрдореНрдкрд▓реЗрдЯ рдбрд╛рд▓реЗрдВ рдФрд░ рдЕрдиреБрдХреВрд▓рд┐рдд рдХрд░реЗрдВред рдпрд╣ рдкрд░рд┐рджреГрд╢реНрдп рдХреЛрдб рд╕реНрдирд┐рдкреЗрдЯ рдЙрдкрдпреЛрдЧ рдорд╛рдорд▓реЗ рдХреЗ рд╕рдорд╛рди рд╣реИред
  2. рдЕрдкрдирд╛ рдмрдВрдзрди рдмрдирд╛рдУред
  3. рдЯреЗрдореНрдкреНрд▓реЗрдЯ + рдореЙрдбрд▓ рдХреЛ рдмрд╛рдЗрдВрдбрд┐рдВрдЧ рдореЗрдВ рдмрджрд▓рдиреЗ рдХреЗ рд▓рд┐рдП рдореЗрд░реА рдиреЙрдХрдЖрдЙрдЯ-рдШрдЯрдХ рд▓рд╛рдЗрдмреНрд░реЗрд░реА рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░реЗрдВред


рдореИрдВрдиреЗ рддреАрд╕рд░реА рд╡рд┐рдзрд┐ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд┐рдпрд╛ред рдЕрдм рдРрд╕реЗ HTML рдХреЛрдб рдореЗрдВ рдЯреНрд░реА рдЗрдВрд╕рд░реНрдЯ рдХреЛ рдШрдЯрд╛рдпрд╛ рдЧрдпрд╛ рд╣реИ:
 <div data-bind='kc.treeView: {data:data,map:{caption:"name",children:"list"}},kc.assign:tree'></div> 

рдореИрдВрдиреЗ рдХреИрд╕реЗ рдХрд┐рдпрд╛ рдпрд╣ рдПрдХ рдЕрд▓рдЧ рд▓реЗрдЦ рдХреЗ рд▓рд┐рдП рдПрдХ рд╡рд┐рд╖рдп рд╣реИред рдФрд░ рдкрд┐рдЫрд▓реЗ рдЙрджрд╛рд╣рд░рдг рдХреЛ рджреЗрдЦреЗрдВ, рд▓реЗрдХрд┐рди рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдПрдХ рдШрдЯрдХ рдХреЗ рд░реВрдк рдореЗрдВ рдЯреНрд░реА рд╡реНрдпреВ рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рддреЗ рд╣реБрдП, рдЖрдк рдпрд╣рд╛рдВ рдЬреЗрдПрд╕рдлрд┐рд▓реНрдб рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ
рдШрдЯрдХ рд╣реА GitHub рдкрд░ рдЙрдкрд▓рдмреНрдз рд╣реИ ред

Source: https://habr.com/ru/post/In165565/


All Articles