Full Code of genify/nej for AI

master 9c0ceb9755c9 cached
639 files
2.4 MB
656.4k tokens
30 symbols
1 requests
Download .txt
Showing preview only (2,893K chars total). Download the full file or copy to clipboard to get everything.
Repository: genify/nej
Branch: master
Commit: 9c0ceb9755c9
Files: 639
Total size: 2.4 MB

Directory structure:
gitextract_zy1dvyqz/

├── .gitignore
├── CHANGELOG
├── LICENSE
├── README.md
├── bower.json
├── doc/
│   ├── AJAX.md
│   ├── CACHE.md
│   ├── DEPENDENCY.md
│   ├── DISPATCHER.md
│   ├── FAQ.md
│   ├── MESSAGE.md
│   ├── PLATFORM.md
│   ├── TEMPLATE.md
│   ├── WIDGET.md
│   └── guide/
│       ├── Build.Scalable.Web.System/
│       │   ├── Module.md
│       │   └── Platform.md
│       └── Build.Scalable.Web.System.md
├── package.json
├── res/
│   ├── cors/
│   │   ├── cross-origin-0.0.2-sources.jar
│   │   └── cross-origin-0.0.2.jar
│   ├── crossdomain.xml
│   ├── nej_clipboard.swf
│   ├── nej_player_audio.swf
│   ├── nej_proxy_flash.swf
│   ├── nej_proxy_frame.html
│   ├── nej_proxy_upload.html
│   ├── nej_storage.swf
│   ├── nej_upload_image.swf
│   └── src/
│       ├── nej_proxy_frame.html
│       └── nej_proxy_upload.html
└── src/
    ├── base/
    │   ├── .dep
    │   ├── chain.js
    │   ├── config.js
    │   ├── constant.js
    │   ├── demo/
    │   │   ├── define/
    │   │   │   ├── a.css
    │   │   │   ├── a.js
    │   │   │   ├── a.json
    │   │   │   ├── define.html
    │   │   │   └── style.html
    │   │   ├── element.html
    │   │   ├── event.html
    │   │   ├── klass.html
    │   │   ├── test.html
    │   │   └── util.html
    │   ├── element.js
    │   ├── event.js
    │   ├── global.js
    │   ├── klass.js
    │   ├── platform/
    │   │   ├── config.js
    │   │   ├── config.patch.js
    │   │   ├── element.js
    │   │   ├── element.patch.js
    │   │   ├── event.js
    │   │   ├── event.patch.js
    │   │   ├── util.js
    │   │   └── util.patch.js
    │   ├── platform.js
    │   ├── test/
    │   │   ├── config.test.html
    │   │   ├── config.test.js
    │   │   ├── constant.test.html
    │   │   ├── constant.test.js
    │   │   ├── element.test.html
    │   │   ├── element.test.js
    │   │   ├── event.test.html
    │   │   ├── event.test.js
    │   │   ├── global.test.html
    │   │   ├── global.test.js
    │   │   ├── klass.test.html
    │   │   ├── klass.test.js
    │   │   ├── platform.test.html
    │   │   ├── platform.test.js
    │   │   ├── util.test.html
    │   │   └── util.test.js
    │   └── util.js
    ├── define.js
    ├── ui/
    │   ├── audio/
    │   │   ├── audio.css
    │   │   ├── audio.html
    │   │   ├── audio.js
    │   │   ├── mp3.css
    │   │   ├── mp3.html
    │   │   ├── mp3.js
    │   │   └── test/
    │   │       ├── audio.test.html
    │   │       ├── audio.test.js
    │   │       ├── mp3.test.html
    │   │       └── mp3.test.js
    │   ├── base.js
    │   ├── carousel/
    │   │   ├── carousel.js
    │   │   ├── carousel.list.js
    │   │   ├── carousel.x.js
    │   │   ├── carousel.y.js
    │   │   ├── indicator.js
    │   │   ├── list.js
    │   │   ├── test/
    │   │   │   ├── carousel.test.html
    │   │   │   └── carousel.test.js
    │   │   ├── x.js
    │   │   └── y.js
    │   ├── carousel.list/
    │   │   └── carousel.list.js
    │   ├── colorpick/
    │   │   ├── colorpanel.css
    │   │   ├── colorpanel.html
    │   │   ├── colorpanel.js
    │   │   ├── colorpick.complex.js
    │   │   ├── colorpick.css
    │   │   ├── colorpick.html
    │   │   ├── colorpick.js
    │   │   ├── colorpick.simple.js
    │   │   ├── complex.html
    │   │   ├── complex.js
    │   │   ├── demo/
    │   │   │   ├── colorpanel.html
    │   │   │   ├── colorpick.html
    │   │   │   ├── complex.html
    │   │   │   └── simple.html
    │   │   ├── simple.css
    │   │   ├── simple.html
    │   │   ├── simple.js
    │   │   └── test/
    │   │       ├── colorpanel.test.html
    │   │       └── colorpanel.test.js
    │   ├── datepick/
    │   │   ├── datepick.css
    │   │   ├── datepick.html
    │   │   ├── datepick.js
    │   │   ├── demo/
    │   │   │   └── datepick.html
    │   │   └── test/
    │   │       ├── datepick.test.html
    │   │       └── datepick.test.js
    │   ├── editor/
    │   │   ├── command/
    │   │   │   ├── color.complex.js
    │   │   │   ├── color.css
    │   │   │   ├── color.js
    │   │   │   ├── color.simple.js
    │   │   │   ├── complex.css
    │   │   │   ├── complex.js
    │   │   │   ├── font.css
    │   │   │   ├── font.html
    │   │   │   ├── font.js
    │   │   │   ├── fontname.js
    │   │   │   ├── fontsize.js
    │   │   │   ├── link.css
    │   │   │   ├── link.html
    │   │   │   ├── link.js
    │   │   │   ├── simple.css
    │   │   │   ├── simple.js
    │   │   │   ├── uploadimage.css
    │   │   │   ├── uploadimage.html
    │   │   │   └── uploadimage.js
    │   │   ├── custom.js
    │   │   ├── demo/
    │   │   │   └── custom.html
    │   │   ├── editor.css
    │   │   ├── editor.html
    │   │   ├── editor.js
    │   │   └── test/
    │   │       ├── custom.test.html
    │   │       └── custom.test.js
    │   ├── item/
    │   │   ├── item.js
    │   │   └── list.js
    │   ├── layer/
    │   │   ├── card.css
    │   │   ├── card.js
    │   │   ├── card.wrapper.js
    │   │   ├── demo/
    │   │   │   ├── card.html
    │   │   │   └── window.html
    │   │   ├── layer.js
    │   │   ├── layer.wrapper.js
    │   │   ├── test/
    │   │   │   ├── card.test.html
    │   │   │   ├── card.test.js
    │   │   │   ├── layer.test.html
    │   │   │   ├── layer.test.js
    │   │   │   ├── mylayer.js
    │   │   │   ├── mylayercard.js
    │   │   │   ├── mylayerwrapper.js
    │   │   │   ├── mywindow.js
    │   │   │   ├── window.test.html
    │   │   │   ├── window.test.js
    │   │   │   ├── window.wrapper.test.html
    │   │   │   └── window.wrapper.test.js
    │   │   ├── window.css
    │   │   ├── window.html
    │   │   ├── window.js
    │   │   ├── window.wrapper.js
    │   │   └── wrapper/
    │   │       ├── card.js
    │   │       ├── layer.js
    │   │       └── window.js
    │   ├── lightbox/
    │   │   ├── demo/
    │   │   │   └── lightbox.html
    │   │   ├── lightbox.css
    │   │   ├── lightbox.html
    │   │   └── lightbox.js
    │   ├── mask/
    │   │   ├── mask.css
    │   │   ├── mask.js
    │   │   └── test/
    │   │       ├── mask.test.html
    │   │       └── mask.test.js
    │   ├── pager/
    │   │   ├── base.css
    │   │   ├── base.js
    │   │   ├── demo/
    │   │   │   └── pager.html
    │   │   ├── pager.base.js
    │   │   ├── pager.js
    │   │   ├── pager.simple.js
    │   │   ├── simple.js
    │   │   └── test/
    │   │       ├── pager.test.html
    │   │       └── pager.test.js
    │   ├── portrait/
    │   │   ├── complex.css
    │   │   ├── complex.html
    │   │   ├── complex.js
    │   │   ├── demo/
    │   │   │   ├── complex.html
    │   │   │   └── simple.html
    │   │   ├── portrait.complex.js
    │   │   ├── portrait.css
    │   │   ├── portrait.html
    │   │   ├── portrait.js
    │   │   ├── portrait.simple.js
    │   │   └── simple.js
    │   ├── range/
    │   │   ├── range.css
    │   │   └── range.js
    │   ├── resizer/
    │   │   ├── demo/
    │   │   │   └── resizer.html
    │   │   ├── resizer.css
    │   │   ├── resizer.html
    │   │   └── resizer.js
    │   ├── scroller/
    │   │   ├── demo/
    │   │   │   └── y.html
    │   │   ├── list.css
    │   │   ├── list.js
    │   │   ├── scroller.js
    │   │   ├── scroller.x.js
    │   │   ├── scroller.y.js
    │   │   ├── test/
    │   │   │   ├── scroller.y.test.html
    │   │   │   └── scroller.y.test.js
    │   │   ├── x.css
    │   │   ├── x.js
    │   │   ├── y.css
    │   │   └── y.js
    │   ├── scroller.list/
    │   │   ├── scroller.list.js
    │   │   └── test/
    │   │       ├── scroller.list.test.html
    │   │       └── scroller.list.test.js
    │   ├── suggest/
    │   │   ├── demo/
    │   │   │   └── suggest.html
    │   │   ├── suggest.css
    │   │   ├── suggest.html
    │   │   ├── suggest.js
    │   │   └── test/
    │   │       ├── suggest.test.html
    │   │       └── suggest.test.js
    │   └── timepick/
    │       ├── demo/
    │       │   └── timepick.html
    │       ├── test/
    │       │   ├── timepick.test.html
    │       │   └── timepick.test.js
    │       ├── timepick.css
    │       ├── timepick.html
    │       └── timepick.js
    └── util/
        ├── ajax/
        │   ├── demo/
        │   │   ├── a.html
        │   │   ├── a.js
        │   │   ├── b.html
        │   │   ├── b.json
        │   │   ├── rest.html
        │   │   ├── tag.html
        │   │   ├── upload.html
        │   │   └── xdr.html
        │   ├── dwr.js
        │   ├── jsonp.js
        │   ├── loader/
        │   │   ├── html.js
        │   │   ├── loader.js
        │   │   ├── platform/
        │   │   │   ├── html.js
        │   │   │   └── html.patch.js
        │   │   ├── script.js
        │   │   ├── style.js
        │   │   ├── test/
        │   │   │   ├── loader.test.html
        │   │   │   └── loader.test.js
        │   │   └── text.js
        │   ├── message.js
        │   ├── platform/
        │   │   ├── message.js
        │   │   ├── message.patch.js
        │   │   ├── xdr.js
        │   │   └── xdr.patch.js
        │   ├── proxy/
        │   │   ├── flash.js
        │   │   ├── frame.js
        │   │   ├── platform/
        │   │   │   ├── xhr.js
        │   │   │   └── xhr.patch.js
        │   │   ├── proxy.js
        │   │   ├── upload.js
        │   │   └── xhr.js
        │   ├── rest.js
        │   ├── tag.js
        │   ├── test/
        │   │   ├── a.html
        │   │   ├── a.js
        │   │   ├── b.html
        │   │   ├── b.js
        │   │   ├── message.test.html
        │   │   ├── message.test.js
        │   │   ├── tag.test.html
        │   │   ├── tag.test.js
        │   │   ├── x.css
        │   │   ├── xdr.test.html
        │   │   ├── xdr.test.js
        │   │   ├── xx.css
        │   │   └── xxx.txt
        │   └── xdr.js
        ├── animation/
        │   ├── animation.js
        │   ├── bezier.js
        │   ├── bounce.js
        │   ├── decelerate.js
        │   ├── demo/
        │   │   └── easeout.html
        │   ├── easein.js
        │   ├── easeinout.js
        │   ├── easeout.js
        │   ├── linear.js
        │   └── test/
        │       ├── animation.test.html
        │       └── animation.test.js
        ├── audio/
        │   ├── audio.js
        │   ├── demo/
        │   │   ├── a.aac
        │   │   ├── a.amr
        │   │   └── audio.html
        │   ├── platform/
        │   │   ├── audio.js
        │   │   └── audio.patch.js
        │   └── test/
        │       ├── audio.test.html
        │       └── audio.test.js
        ├── cache/
        │   ├── abstract.js
        │   ├── cache.js
        │   ├── cache.list.base.js
        │   ├── cache.list.js
        │   ├── cache.share.js
        │   ├── cookie.js
        │   ├── database.js
        │   ├── demo/
        │   │   ├── cookie.html
        │   │   ├── list.html
        │   │   ├── list.js
        │   │   └── storage.html
        │   ├── list.js
        │   ├── manager.js
        │   ├── platform/
        │   │   ├── storage.js
        │   │   └── storage.patch.js
        │   ├── share.js
        │   ├── storage.js
        │   └── test/
        │       ├── cache.custom.js
        │       ├── cache.list.custom.js
        │       ├── cache.test.html
        │       ├── cache.test.js
        │       ├── cookie.test.html
        │       ├── cookie.test.js
        │       ├── storage.test.html
        │       └── storage.test.js
        ├── calendar/
        │   └── calendar.js
        ├── chain/
        │   ├── NodeList.js
        │   ├── README.md
        │   ├── chainable.js
        │   └── test/
        │       ├── chainable.test.html
        │       ├── chainable.test.js
        │       ├── fixture.test.html
        │       ├── playjs.sublime-project
        │       └── playjs.sublime-workspace
        ├── chart/
        │   └── chart.js
        ├── clipboard/
        │   ├── clipboard.js
        │   ├── demo/
        │   │   └── clipboard.html
        │   └── test/
        │       ├── clipboard.test.html
        │       └── clipboard.test.js
        ├── clipper/
        │   ├── clipper.js
        │   └── demo/
        │       └── clipper.html
        ├── clock/
        │   └── clock.js
        ├── color/
        │   ├── color.js
        │   └── demo/
        │       └── color.html
        ├── counter/
        │   ├── counter.js
        │   ├── demo/
        │   │   └── counter.html
        │   ├── platform/
        │   │   ├── counter.js
        │   │   └── counter.patch.js
        │   └── test/
        │       ├── counter.test.html
        │       └── counter.test.js
        ├── cursor/
        │   ├── cursor.js
        │   ├── demo/
        │   │   └── cursor.html
        │   └── platform/
        │       ├── cursor.js
        │       └── cursor.patch.js
        ├── cycler/
        │   ├── cycler.js
        │   └── test/
        │       ├── cycler.test.html
        │       └── cycler.test.js
        ├── data/
        │   ├── portrait/
        │   │   └── portrait.js
        │   └── region/
        │       └── zh.js
        ├── dispatcher/
        │   ├── dispatcher.2.js
        │   ├── dispatcher.js
        │   ├── dsp/
        │   │   ├── group.js
        │   │   ├── node.js
        │   │   ├── single.js
        │   │   └── util.js
        │   ├── module.base.js
        │   ├── module.js
        │   ├── platform/
        │   │   ├── dispatcher.js
        │   │   └── dispatcher.patch.js
        │   ├── regularModule.js
        │   ├── test/
        │   │   ├── c/
        │   │   │   ├── c1.js
        │   │   │   ├── c2.js
        │   │   │   ├── index.css
        │   │   │   └── index.js
        │   │   ├── dispatcher.2.test.html
        │   │   ├── dispatcher.2.test.js
        │   │   ├── m/
        │   │   │   ├── a.html
        │   │   │   ├── b.html
        │   │   │   ├── c.html
        │   │   │   ├── c1.html
        │   │   │   ├── c2.html
        │   │   │   └── root.html
        │   │   ├── private.module.test.html
        │   │   ├── private.module.test.js
        │   │   └── root/
        │   │       ├── index.css
        │   │       └── index.js
        │   └── test.js
        ├── dragger/
        │   ├── dragger.js
        │   ├── simple.js
        │   └── test/
        │       ├── dragger.test.html
        │       └── dragger.test.js
        ├── editor/
        │   ├── area.js
        │   ├── command/
        │   │   ├── backcolor.js
        │   │   ├── blockquote.js
        │   │   ├── bold.js
        │   │   ├── card.js
        │   │   ├── color.js
        │   │   ├── font.js
        │   │   ├── fontname.js
        │   │   ├── fontsize.js
        │   │   ├── forecolor.js
        │   │   ├── format.js
        │   │   ├── insertorderedlist.js
        │   │   ├── insertunorderedlist.js
        │   │   ├── italic.js
        │   │   ├── justifycenter.js
        │   │   ├── justifyleft.js
        │   │   ├── justifyright.js
        │   │   ├── link.js
        │   │   ├── removeformat.js
        │   │   ├── simple.js
        │   │   ├── space.js
        │   │   ├── strikethrough.js
        │   │   ├── superscript.js
        │   │   ├── underline.js
        │   │   └── uploadimage.js
        │   ├── command.js
        │   ├── demo/
        │   │   └── text.html
        │   ├── editor.js
        │   ├── platform/
        │   │   ├── editor.js
        │   │   ├── editor.patch.js
        │   │   ├── editor.td.js
        │   │   └── text.js
        │   ├── text.html
        │   ├── text.js
        │   └── toolbar.js
        ├── effect/
        │   ├── api.js
        │   ├── effect.api.js
        │   ├── effect.js
        │   ├── platform/
        │   │   ├── effect.api.js
        │   │   ├── effect.api.patch.js
        │   │   ├── effect.js
        │   │   └── effect.patch.js
        │   └── test/
        │       ├── effcet.api.test.html
        │       ├── effect.api.test.js
        │       ├── effect.test.html
        │       └── effect.test.js
        ├── encode/
        │   ├── base64.js
        │   ├── crc32.js
        │   ├── demo/
        │   │   ├── json.html
        │   │   └── md5.html
        │   ├── json.js
        │   ├── md5.js
        │   ├── platform/
        │   │   ├── 3rd.json.js
        │   │   ├── json.js
        │   │   └── json.patch.js
        │   ├── sha.md5.js
        │   └── test/
        │       ├── base64.test.html
        │       └── base64.test.js
        ├── es/
        │   ├── array.js
        │   ├── demo/
        │   │   └── array.html
        │   └── platform/
        │       ├── array.js
        │       └── array.patch.js
        ├── event/
        │   ├── esb.js
        │   └── event.js
        ├── event.js
        ├── file/
        │   ├── demo/
        │   │   ├── file.html
        │   │   ├── paste.html
        │   │   ├── select.html
        │   │   └── upload
        │   ├── paste.js
        │   ├── platform/
        │   │   ├── paste.js
        │   │   ├── paste.patch.js
        │   │   ├── select.js
        │   │   └── select.patch.js
        │   ├── save.js
        │   ├── select.js
        │   └── test/
        │       ├── save.test.html
        │       ├── save.test.js
        │       ├── select.test.html
        │       └── select.test.js
        ├── flash/
        │   ├── flash.html
        │   ├── flash.js
        │   ├── platform/
        │   │   ├── flash.js
        │   │   └── flash.patch.js
        │   └── test/
        │       ├── flash.test.html
        │       └── flash.test.js
        ├── focus/
        │   ├── focus.js
        │   ├── platform/
        │   │   ├── focus.js
        │   │   └── focus.patch.js
        │   └── test/
        │       ├── focus.test.html
        │       └── focus.test.js
        ├── form/
        │   ├── demo/
        │   │   └── form.html
        │   ├── form.js
        │   └── test/
        │       ├── form.test.html
        │       └── form.test.js
        ├── gestrue/
        │   ├── drag.js
        │   ├── gestrue.js
        │   ├── pinch.js
        │   ├── rotate.js
        │   ├── swipe.js
        │   └── tap.js
        ├── helper/
        │   └── select.js
        ├── highlight/
        │   ├── test/
        │   │   ├── highlight.test.html
        │   │   └── highlight.test.js
        │   └── touch.js
        ├── history/
        │   ├── history.js
        │   ├── history.override.js
        │   ├── manager.js
        │   └── platform/
        │       ├── history.js
        │       └── history.patch.js
        ├── hover/
        │   ├── hover.js
        │   ├── platform/
        │   │   ├── hover.js
        │   │   └── hover.patch.js
        │   └── test/
        │       ├── hover.test.html
        │       └── hover.test.js
        ├── lazy/
        │   ├── demo/
        │   │   └── image.html
        │   ├── image.js
        │   └── loading.js
        ├── lightbox/
        │   ├── demo/
        │   │   └── lightbox.html
        │   └── lightbox.js
        ├── list/
        │   ├── demo/
        │   │   ├── data.js
        │   │   ├── list/
        │   │   │   ├── cache.js
        │   │   │   ├── item.js
        │   │   │   ├── jst.html
        │   │   │   ├── ntp.html
        │   │   │   ├── pg.html
        │   │   │   └── wf.html
        │   │   ├── page.html
        │   │   └── waterfall.html
        │   ├── holder.js
        │   ├── module.js
        │   ├── module.pager.js
        │   ├── module.waterfall.js
        │   ├── page.js
        │   ├── test/
        │   │   ├── cache.list.custom.js
        │   │   ├── module.pager.test.html
        │   │   └── module.pager.test.js
        │   └── waterfall.js
        ├── media/
        │   ├── audio.js
        │   ├── flash.js
        │   ├── media.js
        │   ├── playlist.js
        │   └── test/
        │       ├── audio.test.html
        │       └── audio.test.js
        ├── page/
        │   ├── base.js
        │   ├── page.base.js
        │   ├── page.js
        │   ├── page.simple.js
        │   └── simple.js
        ├── placeholder/
        │   ├── demo/
        │   │   └── placeholder.html
        │   ├── placeholder.js
        │   ├── platform/
        │   │   ├── holder.js
        │   │   └── holder.patch.js
        │   └── test/
        │       ├── placeholder.test.html
        │       └── placeholder.test.js
        ├── profile/
        │   └── profile.js
        ├── query/
        │   ├── demo/
        │   │   └── demo.html
        │   ├── nes.js
        │   └── query.js
        ├── range/
        │   ├── demo/
        │   │   └── range.html
        │   ├── range.js
        │   └── test/
        │       ├── range.test.html
        │       └── range.test.js
        ├── region/
        │   ├── demo/
        │   │   └── at.html
        │   ├── region.zh.js
        │   ├── test/
        │   │   ├── region.zh.test.html
        │   │   └── region.zh.test.js
        │   └── zh.js
        ├── resize/
        │   ├── demo/
        │   │   └── resize.html
        │   ├── resize.js
        │   └── test/
        │       ├── resize.test.html
        │       └── resize.test.js
        ├── scroll/
        │   ├── demo/
        │   │   ├── simple.html
        │   │   └── smart.html
        │   ├── platform/
        │   │   ├── simple.js
        │   │   └── simple.patch.js
        │   ├── scroll.simple.js
        │   ├── simple.js
        │   └── smart.js
        ├── selector/
        │   ├── cascade.js
        │   ├── demo/
        │   │   ├── cascade.html
        │   │   └── selector.html
        │   ├── range.js
        │   ├── selector.js
        │   └── selector.range.js
        ├── slider/
        │   ├── demo/
        │   │   ├── simple.html
        │   │   └── y.html
        │   ├── simple.js
        │   ├── slider.js
        │   ├── slider.simple.js
        │   ├── slider.x.js
        │   ├── slider.xy.js
        │   ├── slider.y.js
        │   ├── test/
        │   │   ├── slider.test.html
        │   │   ├── slider.test.js
        │   │   └── sorter.test.js
        │   ├── x.js
        │   ├── xy.js
        │   └── y.js
        ├── sort/
        │   ├── demo/
        │   │   ├── horizontal.html
        │   │   ├── horizontal.trigger.html
        │   │   ├── vertical.html
        │   │   └── vertical.trigger.html
        │   ├── horizontal.js
        │   ├── sortable.js
        │   └── vertical.js
        ├── suggest/
        │   ├── at.js
        │   ├── demo/
        │   │   ├── at.html
        │   │   └── suggest.html
        │   ├── suggest.js
        │   └── test/
        │       ├── suggest.test.html
        │       └── suggest.test.js
        ├── tab/
        │   ├── tab.js
        │   ├── tab.view.js
        │   ├── test/
        │   │   ├── tab.test.html
        │   │   └── tab.test.js
        │   └── view.js
        ├── template/
        │   ├── demo/
        │   │   ├── a.css
        │   │   ├── a.html
        │   │   ├── a.js
        │   │   ├── b.css
        │   │   ├── b.html
        │   │   ├── b.js
        │   │   ├── jst.html
        │   │   └── tpl.html
        │   ├── jst.js
        │   ├── test/
        │   │   ├── jst.test.html
        │   │   ├── jst.test.js
        │   │   ├── myItem.js
        │   │   ├── tpl.test.html
        │   │   └── tpl.test.js
        │   ├── tpl.js
        │   └── trimpath.js
        ├── timer/
        │   ├── animation.js
        │   ├── demo/
        │   │   ├── a.js
        │   │   ├── output.js
        │   │   ├── test.html
        │   │   └── test.min.html
        │   ├── interval.js
        │   └── platform/
        │       ├── animation.js
        │       └── animation.patch.js
        └── toggle/
            ├── demo/
            │   └── toggle.html
            ├── test/
            │   ├── toggle.test.html
            │   └── toggle.test.js
            └── toggle.js

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
.settings
.project
.svn
.idea/

================================================
FILE: CHANGELOG
================================================
0.5.2   (2019-01-30)
--------------------
- 功能新增
  * util/ajax/dwr 在请求异常时控制台输出请求信息
- BUG修复模块
  * 修正低版本跨域代理文件发送请求时先序列化对象
  * 修正 util/cache/list 模块隐式声明导致的全局变量污染

0.5.1   (2018-10-11)
--------------------------------------------------------
- BUG修复模块
  * 修正 util/ajax/rest 无法发送 null 数据的问题
  * 修正 util/animation/animation 在 safari12 中当前时间异常 bug

0.5.0   (2018-06-22)
--------------------------------------------------------
- BUG修复模块
  * 修正 util/cache/list 模块 GC 异常
  * 修正 util/template/trimpath 重复引入异常

0.4.9   (2018-04-12)
--------------------------------------------------------
- 功能支持
  * 支持 res 依赖插件注入地址

0.4.7   (2018-04-12)
--------------------------------------------------------
- BUG修复模块
  * base/util 模块的 _$absolute 接口支持相对协议
  * util/timer/animation 的 requestAnimationFrame 接口返回标识

0.4.6   (2017-11-09)
--------------------------------------------------------
- BUG修复模块
  * base/util 模块的 _$url2origin 接口支持相对协议
  * util/ajax/loader/text 修正 XHR 重复加载文本内容时被缓存问题

0.4.5   (2017-09-14)
--------------------------------------------------------
- 功能支持
  * util/ajax/tag 模块增加 _$loadTemplate 接口需要跨域载入的时候采用 ajax 先加载文本再解析内容

- 功能变化
  * util/ajax/tag 模块 _$loadHtml 接口回退不支持跨域载入

0.4.4   (2017-09-12)
--------------------------------------------------------
- 功能支持
  * util/dispatcher/dispatcher 增加根据别名取模块 UMI 接口 _$getUMIByAlias
  * util/cursor/cursor 增加 _$lineno 支持获取当前光标所在的行
  * util/template/tpl _$parseTemplate 支持 type 为 nej/* 的 script 标签解析

- 功能变化
  * util/cache/list _$addItem 接口支持批量添加列表项
  * util/ajax/tag 模块 _$loadHtml 接口需要跨域载入的时候采用 ajax 先加载文本再解析内容
  * NEJ.define 处理过程忽略 NEJ 模板封装脚本,即 type="nej/*" 的 script 标签

0.4.3   (2017-06-26)
--------------------------------------------------------
- 功能支持
  * 添加消息总线机制

0.4.2   (2017-06-05)
--------------------------------------------------------
- 功能变化
  * util/ajax/rest 支持从输入的data中解析地址上的rest变量
  * util/ajax/rest 支持 :PARAM 的地址变量格式
  * util/cache/abstract 支持在 __doSendRequest 的hook接口中传入当前请求key
  * base/element getChildren接口兼容IOS8的XML DOM


0.4.1   (2017-06-01)
--------------------------------------------------------
- 功能变化
  * bower 安装时忽略 test,doc,res,demo 目录

0.4.0   (2017-04-10)
--------------------------------------------------------
- 功能支持
  * 列表删除cache中回传服务器返回的结果

- BUG修复模块
  * util/flash/flash 删除flash检测次数限制

0.3.9   (2017-03-22)
--------------------------------------------------------
- BUG修复模块
  * 修复相对协议模块载入地址错误 (支持https)
  * 修复platform模块无法判断Google Chrome for IOS
  * 修复列表取单项详情时候传入的主键名称为 id

0.3.8   (2017-02-09)
--------------------------------------------------------
- 功能支持
  * 剪切板支持跨域flash引入
  * NEJ 模块文件载入支持文件名版本号

- BUG修复模块
  * 修复 regularModule build 参数输入


0.3.7   (2017-01-09)
--------------------------------------------------------
- 功能支持
  * 支持缓存列表填充验证时重写列表项验证逻辑
  * 支持缓存在 filter 阶段直接返回结果阻止发送请求
  * 增加 __getListReqKey/__getItemReqKey 取请求标识
  * 支持 __doSendRequest 时使用 options 配置请求地址
  * __doSendRequest 过程的事件支持输入 cnf 配置信息对象

- BUG修复模块
  * 修正 util/cache/list 下 getList/getItem 的 ext 参数回传异常


0.3.5   (2016-11-22)
--------------------------------------------------------
- BUG修复模块
  * 修正 IE8 下取 DOM 属性异常问题


0.3.4   (2016-11-16)
--------------------------------------------------------
- 功能支持
  * 支持缓存配置 type 属性

0.3.3   (2016-10-31)
--------------------------------------------------------
- 功能支持
  * 缓存基类支持 _$dump 静态接口
  * 支持 JSONP 接口调用

- BUG修复模块
  * cache 基类 __doSendRequest 接口判空逻辑调整
  * XDR 请求时针对字符串数据序列化的问题

0.3.2   (2016-10-12)
--------------------------------------------------------
- NEJ.define 注入增加 css 插件支持

0.3.1   (2016-08-27)
--------------------------------------------------------
- BUG修复模块
  * util/dispatcher/module 修复 regularModule init 参数
  * 删除文档中单页模块的 template 打包标记

0.3.0   (2016-07-18)
--------------------------------------------------------
- BUG修复模块
  * base/klass 清除无效条件判断
  * base/util string2object/object2string接口处理不合法URL异常
  * util/cache/list 清除请求队列异常
  * util/module/list 瀑布流列表分页问题
  * define.js 在head中引入的异常

- 功能支持
  * util/dispatcher/dispatcher 重定向时模块退出判断支持
  * util/dispatcher/module 模块构建时支持UMI配置参数

0.2.8   (2016-03-29)
0.2.7
--------------------------------------------------------
- 增加regularjs模块的支持
  * util/dispatcher/regularModule.js
- 功能增加
  * base/element 增加 _$getParent 接口
  * util/list/module 增加 _$nextPage,_$prevPage,_$pageTo 接口

0.2.6   (2015-09-07)
0.2.5
--------------------------------------------------------
- 兼容处理
  * define.js识别nej内敛函数判断条件调整,支持 NEJ.define和define函数
  * util/cache/cache兼容到nej.ut._$$Cache类名
  * util/dispatcher/module兼容到nej.ut._$$Module类名
  * 删除XDR请求回调中的try/catch,由应用逻辑自己保证批量请求的异常处理
  * util/sortable/sortable在mousemove事件中开始拖拽

0.2.4   (2015-01-20)
--------------------------------------------------------
- bug修正
  * 修正模块调度器在带flash的ie下标题出现hash串的问题
  * 修正在define.js之后内联非NEJ脚本的依赖异常

0.2.3
--------------------------------------------------------
- bug修正
  * 修正IE9下placeholder异常
  * 修正IE6下弹层无法盖住select问题
  * 修正日历在跨年选择时年份错误问题
- 功能支持
  * webform支持多选select

0.2.2   (2014-11-18)
--------------------------------------------------------
- bug修正
  * base/util._$encode
- tag for 20141118

0.2.1   (2014-11-13)
--------------------------------------------------------
- 增加API/控件支持
  * ui/lightbox/lightbox
  * util/lightbox/lightbox
- webform组件对date类型的表单元素增加data-time支持

0.2.0   (2014-11-05)
--------------------------------------------------------
- 修正define对实例返回结果的注入
- 增加json!类型的插件注入
- 增加regularjs!类型的插件注入
- 增加API/控件支持
  * util/selector/cascade
  * util/suggest/at
  * util/sort/*
  * util/es/array
  * util/lazy/*
  * util/scroll/smart

0.1.0   (2014-09-01)
--------------------------------------------------------
- 重构类模型,__super作为所有接口调用父类同名接口的方式
- 独立类模型实现文件至{lib}base/klass.js
- 支持AMD形式的路径,如base/klass,pro/extend/api
- 支持纯文本资源的依赖载入
- 增加依赖注入,保留兼容模式下名字空间形式
- 独立平台适配,删除patched目录,独立到各API、控件
- 清理没用控件或者后期重新开发控件及API
  * 删除native
  * 删除util/xmpp
  * 删除util/gesture
  * 删除util/ajax/connect.js
  * 删除util/cache/config.js
  * 删除util/dispatch/v1
  * 删除util/module
  * 删除util/tb.resizer
  * 删除util/oauth
  * 删除util/sorter
  * 删除ui/arrows
  * 删除ui/loading
  * 删除ui/loadmore
  * 删除ui/pullrefresh
  * 删除ui/sorter
  * util/cache/storage.manager.js 更名为 util/cache/manager.js

define.js
- NEJ.config接口名称改为NEJ.deps
- 删除define.cmp.js兼容支持

base/util.js
- 删除_$mergeList
- 迁移_$dom2object、_$xml2object至{lib}base/element.js
- 增加接口_$query、_$merge、_$fetch替代原NEJ.Q、NEJ.X、NEJ.EX

base/element.js
- 交换_$getMaxBox接口输入参数顺序
- 修改_$dom2object第二个参数为非必传参数
- 独立_$highlight接口至util/highlight/highlight
- 独立_$hover接口至util/hover/hover
- 独立_$focus接口至util/focus/focus
- 独立_$toggle接口至util/toggle/toggle
- 独立_$cursor接口至util/cursor/cursor

base/platform.js
- 删除_$NOT_PATCH
- 增加_$support/_$is接口

base/event.js
- 删除接口_$stopClick

util/event.js
- 更改_$$Event类名为_$$EventTarget,兼容_$$Event
- _$addEvent接口功能使用_$pushEvent代替原来的_$setEvent
- 删除_$api接口

util/ajax/dwr.js
- 删除_$setProxy接口

util/ajax/tag.js
- 修改onloaded载入回调函数名称为onload

util/cache/storage.js
- 删除_$cloneDataInStorage接口

util/dispatcher
- 合并module.2.js和module.base.js
util/dispatcher.2.js
- 删除_$apply接口,使用_$redirect代替

util/page/page.js
- 删除_e._$page接口

util/template/tpl.js
- 删除_e._$setup接口

util/template/jst.js
- 修改接口名称
  _$addHtmlTemplate     -> _$add
  _$getHtmlTemplate     -> _$get
  _$getHtmlTemplateSeed -> _$seed
  _$renderHtmlTemplate  -> _$render
  _$registJSTExt        -> _$extend

util/tab/tab.js
- onchange事件阻止属性nostop改为stopped
- 删除nej.e._$tab接口

util/cache/share.js
- localCache._$addEvent代替原nej.v._$addEvent方式添加事件监听







================================================
FILE: LICENSE
================================================
NEJ is released under the MIT license

Copyright 2012 (c) NetEase, Inc

Uses 
TrimPath  (http://code.google.com/p/trimpath/)
json2.js  (http://www.JSON.org/json2.js)
json_parse.js (https://github.com/douglascrockford/JSON-js)

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

================================================
FILE: README.md
================================================
# NEJ - JavaScript Framework

## 概述 

跨平台WEB前端开发框架,主要提供Web端SDK用于开发Web应用,服务器端SDK用于整合解决方案的服务器端实现

主要特性包括:

* [依赖管理系统](./doc/DEPENDENCY.md)支持 
* [平台适配系统](./doc/PLATFORM.md)支持(浏览器、移动APP、桌面APP等)
* 丰富可灵活扩展的[控件系统](./doc/WIDGET.md)(可验证表单、列表、拖拽、滑块、日历、富文本编辑器等)
* 多方案集成([模板系统](./doc/TEMPLATE.md)、[可组合的模块化开发](./doc/DISPATCHER.md)、[单页系统按需载入](./doc/DISPATCHER.md)、[基于配置的跨域异步请求](./doc/AJAX.md)等)
* 可自由定制的产品发布(按平台定制、按功能定制)
* 工具支持([NEJ工具集](https://github.com/genify/toolkit2)、[NEI工具集](https://github.com/genify/nei)等)
* 新技术整合(对于高端目标平台自动应用新技术)

## 使用 

[API手册](http://nej.netease.com/help/index.html)

页面引入define.js,可以下载到本地也可以使用NEJ服务器上的define.js( http://nej.netease.com/nej2/src/define.js ),通过切换define.js的路径切换使用的NEJ库

```html
<!DOCTYPE html>
<html>
  <head>
    <title>use nej</title>
    <meta charset="utf-8"/>
  </head>
  <body>
    <textarea id="txt-0"></textarea>
    <input type="text" id="input-id-0" maxlength="100"/>
    
    <script src="/path/to/nej/define.js"></script>
    <script>
      NEJ.define([
        'util/counter/counter'
      ],function(_e){
          _e._$counter('txt-0');
          _e._$counter('input-id-0');
          
          // TODO
      });
    </script>
    <script>
      // 习惯链式的同学也可以这样使用
      NEJ.define([
        'util/chain/chainable',
        'util/counter/counter'
      ],function($){
          $('#txt-0')._$counter();
          $('#input-id-0')._$counter();
          
          // TODO
      });
    </script>
  </body>
</html>
```

## 文档

* 依赖管理 [文档](./doc/DEPENDENCY.md)
* 平台适配 [文档](./doc/PLATFORM.md)
* 窗体消息 [文档](./doc/MESSAGE.md)
* 远程调用 [文档](./doc/AJAX.md)
* 模块调度 [文档](./doc/DISPATCHER.md)
* 模板系统 [文档](./doc/TEMPLATE.md)
* 组件系统 [文档](./doc/WIDGET.md)

================================================
FILE: bower.json
================================================
{
    "name": "nej",
    "version": "0.5.2",
    "homepage": "https://github.com/genify/nej",
    "authors": [
        "genify <caijf@corp.netease.com>"
    ],
    "description": "JavaScript Cross-Platform Framework",
    "main": "./src/define.js",
    "keywords": [
        "nej",
        "javascript",
        "cross platform",
        "framework"
    ],
    "ignore": [
        ".*",
        "**/test/",
        "**/demo/",
        "doc",
        "res"
    ],
    "license": "MIT"
}

================================================
FILE: doc/AJAX.md
================================================
# 前后端通讯系统

## 概述

随着富媒体应用的不断发展成熟,AJAX的应用得到越来越多的普及,而AJAX应用的一个核心功能-异步数据载入功能也就显得越为重要,包括HTML5的发展也为此提供了强大的支持。

对于富媒体WEB应用来说异步请求为其必不可缺的组成部分,对于同域的异步请求基本无需做太多额外配置,对于跨域的请求W3C也提供了相应的规范的支持,但是由于各浏览器对规范支持的程度的差异导致最终使用时会有一些差别,而NEJ框架针对这些差异及低版本的跨域支持做了封装,对上层应用而言可以使用统一的API做同域或非同域的异步请求。

## 代理模式

NEJ中主要通过客户端中间代理的方式实现异步请求,根据代理实现方式的差别主要提供以下几种模式的支持,具体应用可以根据实际情况选择使用的代理模式

* W3C的Cross-Origin Resource Sharing规范,后面简称CORS规范
* Frame代理
* Flash代理
* 文件上传代理

NEJ在所有平台均提供了以上三种模式的支持,由于各模式均有自己的一些缺陷,因此实际应用时请按照具体情况选择不同的模式


## CORS规范

CORS规范详细描述见W3C的《[Cross-Origin Resource Sharing](http://www.w3.org/TR/cors/)》部分

### 原理

#### 术语

##### simple method

请求方式为以下几种之一,区分大小写

* GET
* HEAD
* POST

##### simple header

请求头信息只包含以下几种,不区分大小写

* Accept
* Accept-Language
* Content-Language

或者

* Content-Type

但是其值只能为以下几种之一,不区分大小写

* application/x-www-form-urlencoded
* multipart/form-data
* text/plain

##### simple response header

返回头信息包含以下几种,不区分大小写

* Cache-Control
* Content-Language
* Content-Type
* Expires
* Last-Modified
* Pragma

##### user credentials

指定安全相关信息传输方式,包括

* cookies
* HTTP authentication
* client-side SSL certificates

#### 控制字段

各字段构建阶段相见[流程](#流程)部分

##### 请求相关

* [Origin](http://www.w3.org/TR/cors/#origin-request-header)
* [Access-Control-Request-Method](http://www.w3.org/TR/cors/#access-control-request-method-request-header)
* [Access-Control-Request-Headers](http://www.w3.org/TR/cors/#access-control-request-headers-request-header)

##### 返回相关

* [Access-Control-Allow-Origin](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header)
* [Access-Control-Allow-Methods](http://www.w3.org/TR/cors/#access-control-allow-methods-response-header)
* [Access-Control-Allow-Headers](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header)
* [Access-Control-Allow-Credentials](http://www.w3.org/TR/cors/#access-control-allow-credentials-response-header)
* [Access-Control-Max-Age](http://www.w3.org/TR/cors/#access-control-max-age-response-header)
* [Access-Control-Expose-Headers](http://www.w3.org/TR/cors/#access-control-expose-headers-response-header)

#### 流程

##### 流程概况

![CORS请求](http://img0.ph.126.net/R6pynYIBRt0rv2CJvzjhhA==/6608753177213290299.png)

1.  浏览器捕获到a.b.com应用往x.y.com的服务器发起的请求
2.  浏览器检查请求情况确定是否需要先做一次预请求来验证x.y.com的服务器是否允许发当前请求过去,如果需要发预请求则浏览器发起一个OPTIONS的请求到x.y.com的服务器验证继续第3步,否则直接发送请求到x.y.com服务器继续第5步
3.  服务器根据浏览器发过来的header信息,然后根据服务器端对资源的配置返回资源的实际控制权限配置
4.  浏览器验证预请求返回的信息,判断是否可以将请求发送到x.y.com的服务器,如果不行则异常退出,否则继续第5步
5.  浏览器发送实际请求至x.y.com服务器
6.  服务器返回请求数据及资源控制配置信息至浏览器
7.  浏览器验证资源控制信息是否允许当前实际请求的取到数据,如果不允许则异常退出,否则继续第8步
8.  浏览器返回x.y.com返回的数据至a.b.com的应用

##### User Agent相关流程

###### check preflight request

![check preflight request](http://img2.ph.126.net/r-8_Drfm2tKxTGZEDK8_3Q==/2987012452953704123.png)

在没有预请求缓存的情况下,是否发送预请求主要取决于以下两个条件

* 请求方式是否[simple method](#simple method)
* 没有自定义头信息,或者自定义头信息符合[simple header](#simple header)

###### make preflight request

![make preflight request](http://img0.ph.126.net/9_h6lb6N37bvStq5Ze2MFw==/6619176547444246158.png)

这里主要完成以下信息的设置

* [Origin](http://www.w3.org/TR/cors/#origin-request-header)
* [Access-Control-Request-Method](http://www.w3.org/TR/cors/#access-control-request-method-request-header)
* [Access-Control-Request-Headers](http://www.w3.org/TR/cors/#access-control-request-headers-request-header)

###### cache preflight request

![cache preflight request](http://img2.ph.126.net/qxQ_SDJ1oBbWdjgea0r4bw==/4806466702411514477.png)

###### check preflight response

![check preflight response](http://img1.ph.126.net/5tqXaNkLoUQ_ny5yhWpX5w==/1612570141676339530.png)

这里需要注意:如果设置了[user credentials](#user credentials),则返回的Access-Control-Allow-Origin必须与请求的Origin完全一致,包括大小写

##### Server相关流程

###### response preflight request

![response preflight request](http://img0.ph.126.net/STUHWTmsoH_h9V49H9BL-Q==/3301420001939732538.png)

这里主要完成以下信息的设置

* [Access-Control-Allow-Origin](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header)
* [Access-Control-Allow-Credentials](http://www.w3.org/TR/cors/#access-control-allow-credentials-response-header)
* [Access-Control-Max-Age](http://www.w3.org/TR/cors/#access-control-max-age-response-header)
* [Access-Control-Allow-Methods](http://www.w3.org/TR/cors/#access-control-allow-methods-response-header)
* [Access-Control-Allow-Headers](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header)

###### response actual request

![response actual request](http://img2.ph.126.net/3ho2hdTzTKERNY6BTXda5g==/6619581167723268848.png)

这里除了返回[response preflight request](#response preflight request)阶段返回的头信息外,还可以返回Access-Control-Expose-Headers用来控制客户端脚本中可获取的返回头信息

* [Access-Control-Expose-Headers](http://www.w3.org/TR/cors/#access-control-expose-headers-response-header)

### 缺陷

需要高版本浏览器支持,Trident引擎的浏览器需要IE10以上才支持

## Frame代理

此模式主要参照CORS规范原理,通过在目标服务器放置一个代理文件,然后通过该代理文件来与服务器端进行数据交互,返回数据通过消息通讯返回给上层应用来实现跨域的数据交互。

此方式也支持通过代理文件配置资源可访问的来源

### 原理

![Frame代理跨域流程](http://img2.ph.126.net/cPIDakXJZLhpnldZrZpZ7g==/6608721291376084913.png)

1.  当a.b.com的应用要往x.y.com的服务器取数据时,首先会用iframe载入预先放置在x.y.com服务器上的代理文件
2.  服务器端返回做了配置的代理文件
3.  代理文件载入完成后a.b.com的应用将要发送的请求指令通过消息通信方式传递给代理文件
4.  代理文件验证a.b.com是否在预先配置的白名单中,如果不在则异常返回,否则直接发送请求至x.y.com服务器
5.  服务器返回数据至代理文件
6.  代理文件通过消息通讯机制将请求结果返回给a.b.com的应用

### 缺陷

该模式主要有以下几个问题:

* 需要在目标服务器放置代理文件
* 由于首次发起请求时需要载入代理文件,在载入代理文件之前的所有请求都会存在一定的延时
* 对于低版本浏览器受限于消息通讯机制的限制,对于并发量大的请求返回时可能存在较大延时

## Flash代理

此模式与Frame代理模式类似,主要差别在于请求通过Flash来发送,因此可以利用Flash的策略文件crossdomain.xml来控制资源的共享权限

### 原理

![Flash代理跨域流程](http://img1.ph.126.net/HVFJFekgjG23O_z-Fq2yLA==/6608433219329605678.png)

1.  a.b.com的应用从自己的服务器上载入用来做请求的flash代理文件
2.  a.b.com服务器返回代理flash文件
3.  flash准备完毕后a.b.com应用将请求指令发送给flash
4.  flash从目标服务器x.y.com载入策略文件crossdomain.xml
5.  flash验证策略文件是否允许a.b.com访问x.y.com的资源,如果不允许则异常返回,否则发送请求至目标服务器
6.  x.y.com服务器返回数据至flash代理中
7.  flash代理将请求返回的数据提交到a.b.com的应用中

### 缺陷

该模式主要有以下几个问题:

* 需要浏览器支持flash
* 需要在目标服务器配置flash策略文件crossdomain.xml
* 由于首次发起请求时需要载入flash代理文件,在载入代理文件之前的所有请求都会存在一定的延时

## 文件上传

对于无刷新的系统来说,如果需要上传文件则必须也需要不刷新当前页面,对于高版本浏览器使用W3C中定义的XMLHttpRequest规范来实现,低版本则采用Frame代理的方式来实现

### 原理

#### XMLHttpRequest

对于高版本浏览器使用W3C规范中对XMLHttpRequest的定义来实现,相关接口定义

![uploader in XMLHttpRequest](http://img2.ph.126.net/prhGaXl0rn9g7CY3BK1p9A==/6599280884541303788.png)

XMLHttpRequest关于上传相关的数据发送流程

![send in XMLHttpRequest](http://img0.ph.126.net/KMsO751gC-M32-m3e6Qxlw==/6608621235817958265.png)

上传这里主要会采用Blob或FormData数据形式发送到服务器

#### Form+Frame代理

对于无法支持XMLHttpRequest直接进行文件上传的浏览器,采用Form表单+Frame代理的方式来实现

![form+frame upload](http://img1.ph.126.net/TLJiqwe6DY31CZIddXXYTQ==/6619487709234907039.png)

1.  a.b.com应用中包含要上传的文件的Form表单POST至目标服务器x.y.com,如果需要轮询进度的话此时开始起定时器轮询上传进度
2.  x.y.com将返回结果按照nej\_proxy\_upload.html模板文件给定的格式返回结果至Frame代理中
3.  Frame代理通过消息通讯机制将上传结果返回到a.b.com的应用中

### 缺陷

低版本返回的数据格式同高版本的不一致,因此服务器端需额外判断当前请求类别,然后根据类别返回数据内容

## NEJ封装

NEJ作为跨平台的WEB前端开发框架在跨域前后端通讯方面根据以上原理做了较好的封装,上层应用只需使用相应API即可

### 配置

#### 高版本CORS配置

##### 添加依赖

如果是maven工程,在pom.xml文件中加入依赖即可

```xml
<dependency>
    <groupId>com.netease.wd</groupId>
    <artifactId>cross-origin</artifactId>
    <version>0.0.2</version>
</dependency>
```

如果是非maven工程,将jar包加入到工程的classpath中即可,[下载jar包](http://mvn.hz.netease.com/artifactory/webapp/browserepo.html?pathId=libs-releases%3Acom%2Fnetease%2Fwd%2Fcross-origin%2F0.0.2%2Fcross-origin-0.0.2.jar)

##### 配置web.xml

在web.xml文件中引入cross-origin filter的配置,并添加需要的初始化参数

```xml
<filter>
    <filter-name>cross-origin</filter-name>
    <filter-class>com.netease.wd.crossorigin.filter.CrossOriginFilter</filter-class>
</filter>
```

配置参数说明

| 参数名称 | 参数说明 |
| :---     | :---     |
| allowOrigins | 允许访问资源的origin列表。必须是完整的域名,包含scheme, host和port(如果有的话),以,分隔,且区分大小写。 如:http://a.b.com, http://a.b.com:8080。作用于preflight请求以及后面的正式访问请求。访问的请求必须带有Origin header,并且内容要完全匹配列表中的某个值才允许访问后面的资源。如果对origin无限制,则该项可以配置成*。|
| allowMethods | 允许访问资源的method列表,以,分隔,区分大小写。如:GET, POST等。在preflight请求中,必须带有“Access-Control-Request-Method” header,并且值必须与列表中的某个值匹配,才能允许访问后续资源。如果对Method无限制,可以将该项配置成*。 |
| allowHeaders | 访问资源的请求所允许携带的header列表。Filter会检查与preflight请求的“Access-Control-Request-Headers” header,获取请求访问的header列表。Header列表所包含的header必须全部出现在allowHeaders列表中(不区分大小写),才允许访问后续资源。如果对header无限制,可将该项配置为*。 |
| supportCredentials | 是否支持credentials选项,可选值为:true或者false。如果为true,则在响应中加入“Access-Control-Allow-Credentials”为true的header。 |
| maxAge | 指定preflight request缓存的时间长度,单位为秒。配置后,会在响应中加入相应的“Access-Control-Max-Age” header和秒数。 |
| exposeHeaders | 允许expose的header列表,以,分隔。配置该项后,在正式访问请求的响应中会带上“Access-Control-Expose-Header” header。 |
| checkReferer | 是否检查referer header,可选值:true或false。该选项主要用于处理IE某些版本下,不支持CORS并且跨域请求不带Origin header的情况。打开该开关后,如果请求没有Origin header,则会比较Referer和Host来判断请求是否存在跨域。如果有跨域,则从Referer中提取出Origin域名,之后的流程则与前面描述的流程一致。 |

##### 配置url mapping

将需要做CORS处理的资源与filter进行关联即可

```xml
<filter-mapping>
    <filter-name>cross-origin</filter-name>
    <url-pattern>/path/to/resources</url-pattern>
</filter-mapping>
```

##### 使用范例

```xml
<filter>
    <filter-name>cross-origin</filter-name>
    <filter-class>com.netease.wd.crossorigin.filter.CrossOriginFilter</filter-class>
    <init-param>
        <param-name>allowOrigins</param-name>
        <param-value>http://a.b.com, http://a.b.com:8080</param-value>
    </init-param>
    <init-param>
        <param-name>allowMethods</param-name>
        <param-value>GET, POST, OPTIONS</param-value>
    </init-param>
    <init-param>
        <param-name>allowHeaders</param-name>
        <param-value>*</param-value>
    </init-param>
    <init-param>
        <param-name>supportCredentials</param-name>
        <param-value>true</param-value>
    </init-param>
    <init-param>
        <param-name>maxAge</param-name>
        <param-value>60</param-value>
    </init-param>
    <init-param>
        <param-name>exposeHeaders</param-name>
        <param-value>Set-Cookie, Max-Age</param-value>
    </init-param>
    <init-param>
        <param-name>checkReferer</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>cross-origin</filter-name>
    <url-pattern>/path/to/resources</url-pattern>
</filter-mapping>
```

##### 上传返回结果兼容

在上面文件上传部分我们分析发现对于使用不同的上传策略,API需根据策略返回不同的结果,为了减少此方式对应用层的影响,在jar包中提供了统一的方法供使用,主要封装了

* 对于支持CORS的浏览器,服务器只需要返回json字符串
* 对于不支持CORS的浏览器,服务器需要返回特定格式的html页面内容

使用范例

```java
public void doGet(HttpServletRequest request, HttpServletResponse response) {
    // 返回结果序列化为JSON字符串
    String content = "{msg: \"'hello'\"}";
    try {
        // 调用工具方法按具体策略返回结果
        CORSResponseUtil.response(request, response, content);
    } catch (IOException e) {
        e.printStackTrace();
    }
}
```

#### 低版本代理文件配置

##### 代理文件配置

在服务器放置NEJ代理文件,NEJ代理文件可从[这里](https://github.com/genify/nej/tree/master/res)获取,如果代理文件在服务器的/res/下可直接访问,则代码中无需配置,否则在载入NEJ define.js文件之前做好代理配置

```html
<script>
    window.NEJ_CONF = {
        // 多个域名在这里配置
        p_frame:['http://a.b.com/path/to/nej_proxy_frame.html']
    };
</script>
<script src="/path/to/nej/define.js"></script>
```

##### 白名单配置

数据提供方可以通过修改nej\_proxy\_frame.html中的白名单来控制允许调用接口的第三方域名,这里主要以正则表达式方式配置whitelist即可

```html
<script>window.whitelist=[/.*/i];</script>
```

### _$request

\_#request接口在util/ajax/xdr模块中实现

|      | 类型 | 描述 |
| :--  | :--  | :-- |
| 输入 | String | 请求地址 |
|      | Object | 配置信息 |
| 输出 | String | 请求标识 |

配置参数说明

| 参数 | 类型 | 说明 |
| :--  | :--  | :-- |
| sync    | Boolean  |  是否同步请求 |
| type    | String   |  返回数据格式,text/json/xml |
| data    | Variable |  要发送的数据 |
| query   | Variable |  查询参数,字符串格式a=b&c=d,对象格式{a:'b',c:'d'} |
| method  | String   |  请求方式,GET/POST |
| timeout | Number   |  超时时间,0 禁止超时监测 |
| headers | Object   |  头信息表 |
| cookie  | Boolean  |  跨域请求是否带cookie,仅对CORS方式有效 |
| mode    | Number   |  请求模式,针对跨域请求采用的请求方式 |
| result  | Object   |  onload回调输入时需包含的额外信息,已处理额外数据,headers - 服务器返回头信息,如{headers:'x-res-0'}或者{headers:['x-res-0','x-res-1']} |
| onload  | Function |  数据载入回调 |
| onerror | Function |  请求异常回调 |
| onbeforerequest | Function  | 请求之前回调 |

请求模式配置说明

* 0 - 自动模式,高版本使用HTML5的CORS协议,低版本采用Frame代理方式
* 1 - 高版本使用HTML5的CORS协议,低版本采用Flash代理方式
* 2 - 全部使用Frame代理方式
* 3 - 全部使用Flash代理方式

代码举例

```javascript
NEJ.define([
    'util/ajax/xdr'
],function(_j){
    // 从a.b.com载入数据
    _j._$request(
        'http://a.b.com/api/get',{
            type:'json',
            onload:function(_result){
                // TODO
            },
            onerror:function(_error){
                // TODO
            }
        }
    );
});
```

### _$upload

\_$upload接口在util/ajax/xdr模块中实现

|      | 类型 | 描述 |
| :--  | :--  | :-- |
| 输入 | HTMLFormElement | 表单对象,待上传的文件及目标地址信息封装在此对象中 |
|      | Object | 配置信息 |
| 输出 | String | 请求标识 |

配置参数说明

| 参数 | 类型 | 说明 |
| :--  | :--  | :-- |
| type    | String   |  返回数据格式
| query   | Variable |  查询参数
| mode    | Number   |  跨域类型,0/2,见[_$request](#_$request)接口说明
| headers | Object   |  头信息
| cookie  | Boolean  |  跨域请求是否带cookie,仅对CORS方式有效
| onload  | Function |  数据载入回调
| onerror | Function |  请求异常回调
| onuploading | Function |  上传进度回调
| onbeforerequest | Function |  请求之前回调

代码举例

```html
<form id="upload" name="upload" action="http://123.163.com:3000/xhr/uploadCallback">
   <input type="text" id="progress" />
   <!-- 低版本轮询进度接口 -->
   <input type="hidden" name="nej_mode" value="2" />
   <input type="hidden" name="nej_query" value="http://123.163.com:3000/xhr/progress" />
</form>
```

```javascript
NEJ.define([
    'util/ajax/xdr'
],function(_j){
    _j._$upload('upload',{
        mode:2,
        cookie:true,
        onuploading:function(_data){
            // 后台处理http://123.163.com:3000/xhr/progress,返回一个json对象
            // 前台会去轮询此接口获取进度
            if(!!_data.total&&_data.progress){
                _progress.value = _data.progress;
            }
        },
        onload:function(_url){
            // 此前会把进度轮询终止掉。如果要显示进度100%,可在此设置一次
            // 后台处理http://123.163.com:3000/xhr/uploadCallback,返回url
            // 文件上传完成的回调,url为返回的地址
        }
    });
});
```


================================================
FILE: doc/CACHE.md
================================================
# 前端存储机制

## 概述



## 内存缓存




## 本地存储




## 本地数据库






================================================
FILE: doc/DEPENDENCY.md
================================================
# 依赖管理系统

## 概述

随着前端系统的日渐复杂,系统所依赖的文件越来越多,单纯的依靠人肉管理已经无法满足高品质系统的需求了,为了便于开发者管理、老系统平滑升级迁移,NEJ提供了一套文件依赖管理系统,本系统主要特性包括:

* 安全沙箱:文件定义的模块运行在自己的执行环境中,通过返回控制对外开放的接口
* 依赖管理:管理文件之间的依赖关系,包括非脚本的资源文件
* 依赖注入:文件依赖的其他资源可以通过返回结果注入给其依赖文件
* 平滑升级:无脚本侵入,只需通过配置即可在老系统中实现依赖管理
* 打包发布:提供优化输出[打包工具](https://github.com/genify/toolkit.git),分离依赖系统,减少依赖对线上产品的影响

## 使用

页面引入依赖库文件,配置参数通过define.js的查询参数输入

```html
<script src="/path/to/nej/define.js?pro=./"></script>
```

脚本文件定义,为防止与其他加载器命名冲突,NEJ使用NEJ.define接口定义文件执行函数

```javascript
NEJ.define([
    'base/element',
    'pro/extend/util',
    '/path/to/file.js',
    '{platform}patch.js',
    'text!/path/to/file.css',
    'text!/path/to/file.html'
],function(e,u,t,h,css,html,p,o,f,r){

    // TODO something

    // 返回结果可注入给其他文件
    return p;
});
```

## 配置

NEJ的依赖管理系统配置信息通过define.js的查询参数输入,打包时也会根据这些参数配置做相应处理

系统保留单字母命名的参数配置,预置的配置参数如:g、c、d、p

### 预置配置

#### g=[true|false]

配置项目中是否存在全局的define函数,默认为false,如果配置为true则项目中必须使用NEJ.define来使用本依赖管理系统,否则如果项目没有定义额外的define函数则可以使用define函数来使用此依赖系统

```html
<script src="/path/to/nej/define.js?g=true"></script>
```

#### c=[gbk|utf-8|…]

配置项目编码格式,依赖载入的脚本编码格式会设置为此参数配置的信息,打包输出也会参考此参数进行输出,默认为utf-8,如果配置为其他编码格式则需保证项目的相关文件的编码必须统一为此配置的编码格式

```html
<script src="/path/to/nej/define.js?c=gbk"></script>
```

#### d=/patch/to/dep.js

配置第三方库或者遗留项目文件的依赖关系,具体使用可参看NEJ.deps接口的说明

```html
<script src="/path/to/nej/define.js?d=./dep.js"></script>
```

#### p=td|gk|wk
平台配置参数,见以下[平台配置](#平台配置)章节说明


### 路径配置

文件定义时指定的路径可以通过路径配置的变量来指定前缀,如路径配置时指定了变量A,则文件定义时可以直接使用A或者”{A}”来表示A指定的路径前缀。

```html
<script src="/path/to/nej/define.js?A=../web/js/"></script>
```

```javascript
NEJ.define([
    'A/extend/util',
    '{A}extend/util.js'
],function(u1,u2){

    // 这里需要注意的是如果使用{A}形式需要加后缀.js
    // 这里两种方式引入的为同一个文件都是 ../web/js/extend/util.js 文件

});
```

系统默认预置了lib、pro、platform的路径配置

#### lib

NEJ框架路径配置名称为”lib”,文件定义和依赖时可直接使用”{lib}”来表示框架的路径,默认此路径为外联的define.js文件所在的路径

```html
<script src="/path/to/nej/define.js"></script>
```

```javascript
NEJ.define([
    'base/element',
    'lib/base/element',
    '{lib}base/element.js'
],function(e1,e2,e3){

    // 这里解析出来的lib为 /path/to/nej/
    // 以上三种方式访问的为同一个文件都是 /path/to/nej/base/element.js 文件

});

```

#### pro

项目脚本根路径配置名称为“pro”,文件定义和依赖时可直接使用pro或“{pro}”来表示脚本根路径,此配置信息可以通过外联的define.js路径中的查询串进行配置。如果没有自定义配置则默认相对于当前页面访问路径的“../javascript/”路径。

```html
<script src="/path/to/nej/define.js?pro=./"></script>
```

```javascript
NEJ.define([
    'pro/api/element',
    '{pro}/api/element.js'
],function(e1,e2){

    // 以上两种方式访问的为同一个文件都是 ./api/element.js 文件

});

```

#### platform

控件依赖补丁名称为“platform”,只用于文件依赖,使用“{platform}xxx.js”来表示控件依赖的补丁文件,会解析为依赖xxx.js和xxx.patch.js两个文件。xxx.js为W3C/ES规范实现方式,提供所有标准平台支持的公用部分,xxx.patch.js通过NEJ.patch接口提供不同平台对这些接口的特有实现逻辑,这部分内容的详细说明参阅《NEJ平台适配系统》

```
  widget
    | - element.js
    | - platform
            | - element.js
            | - element.patch.js
```

```javascript
NEJ.define([
    '{platform}/element.js'
],function(e1){

    // 这里会自动载入 ./platform/element.js 和 ./platform/element.patch.js 文件

});

```

#### 自定义路径

其他自定义路径可以通过引入define.js文件时作为查询参数输入进行配置,如自定义com路径,则可以通过以下方式进行配置(配置路径中以”./”、”../”起始的相对路径相对于当前地址栏路径)

```html
<script src="/path/to/nej/define.js?com=../js/"></script>
```

```javascript
NEJ.define([
    'com/api/util',
    '{com}api/util.js'
],function(u1,u2){

    // 这里两种方式引入的为同一个文件都是 ../js/api/util.js 文件

});
```

### 平台配置

平台参数在开发及打包过程中都会使用,框架支持平台参数的配置通过define.js路径上查询串中的p参数输入。

平台配置信息,此配置又分两类基本配置:补丁配置和混合配置,因为混合模式下使用的浏览器引擎固定,因此当配置中出现混合类型的配置时忽略补丁配置的值。

如果在引入依赖定义库时未指定平台信息则表示系统需对全平台浏览器支持。

关于平台配置相关的详细内容可参阅《NEJ平台适配系统》

#### 补丁配置

主要用来修正浏览器平台对接口及控件的支持,按照目前浏览器引擎划分,参数值由一个或者多个平台标识组成,标识支持如下所示:

| 标识  | 说明 |
| :---  | :--- |
| gk    | 以gecko为核心的浏览器平台,如firefox等 |
| wk    | 以webkit为核心的浏览器平台,如chrome、safari等 |
| td    | 以trident为核心的浏览器平台,如IE、360、maxthon等 |
| td-0  | 以trident为核心的浏览器平台,且引擎内核版本大于等于3.0,即IE>=7 |
| td-1  | 以trident为核心的浏览器平台,且引擎内核版本大于等于7.0,即IE>=10 |

```html
<script src="/path/to/nej/define.js?p=wk|gk|td"></script>
```

#### 混合配置

主要用于混合开发模式下对native接口的适配,按照native平台划分,参数值由一个标识组成,多个标识则以识别的第一个标识为准,标识支持如下所示:

| 标识 | 说明 |
| :--- | :--- |
| cef       | 基于cef框架混合应用,主要针对桌面应用 |
| ios       | ios平台混合应用,如iphone应用、ipod应用、ipad应用等 |
| win       | windows phone平台混合应用 |
| android   | android平台混合应用 |

```html
<script src="/path/to/nej/define.js?p=cef"></script>
```


## 接口

以下所有接口均使用NEJ作为名字空间,如define接口使用时用NEJ.define

### define

文件定义接口,并指定当前脚本执行所需的其他依赖文件,文件路径规则

* 完整的文件路径,如 http://a.b.com/patch/to/file.js
* 使用{}标识配置参数,如{root}file.js
* 直接使用非{}标识配置参数,此时不能加.js后缀,系统自动加.js后缀,如 root/file
* NEJ库文件可以省略lib标识,如base/element,等价于 lib/base/element,等价于 {lib}base/element.js
* 其他文本资源采用text!前缀标识,如text!/path/to/file.css,注意开发时如果资源是跨域的请设置好浏览器XHR的跨域支持
* JSON资源采用json!前缀标识,如json!/path/to/data.json,注意开发时如果资源是跨域的请设置好浏览器XHR的跨域支持
* Regular模板资源采用regular!前缀标识,如regular!/path/to/file.html,注意开发时如果资源是跨域的请设置好浏览器XHR的跨域支持
* 路径以 ./ 或者 ../ 开始的相对路径则相对于当前脚本文件的路径,如 ./util.js

执行函数注入参数说明

* 注入依赖列表中各文件对应的返回结果
* 注入额外四个参数,依次为p、o、f、r,其中
  * p为输出结果集空间,用于注入到其他执行函数中的内容
  * o为对象实例,即{},用于处理对象默认值,如 var x = options||o;
  * f为函数实例,返回false,用于处理方法默认值,如 var func = x.onready||f;
  * r为数组实例,即[],用于处理数组默认值,如 var arr = options.list||r;

接口说明

|      | 类型 | 描述 |
| :--- | :--- | :--- |
| 输入 | String   |   可选,当前文件路径 |
|      | Array    | 可选,依赖的其他文件路径 |
|      | Function | 可选,当前文件需要执行的脚本,依赖文件的返回结果依次注入到该函数中 |
| 输出 | Object   |   对外开放的接口,可注入到依赖该文件的其他文件执行函数中 |

```javascript
NEJ.define([
    'util/ajax/tag',
    'util/template/jst'
],function(j,e,p,o,f,r){

    // j 为 util/ajax/tag 文件导出的接口集合
    // e 为 util/template/jst 文件导出的接口集合

    // 用于注入到其他文件执行函数中的接口
    p.api = function(){

    };

    // TODO something

    return p;
});
```

### patch

平台补丁配置接口,用于针对不同平台做修正逻辑,平台支持TR|WR|GR,没有比较操作符表示支持当前内核所有release版本,平台引擎标识说明如下

| 标识  | 说明 |
| :---  | :--- |
| T     | Trident引擎,如IE |
| W     | Webkit引擎,如chrome |
| G     | Gecko引擎,如firefox |

内置的Trident引擎版本对应的IE版本关系

| Trident版本 | IE版本 |
| :---  | :--- |
| 2.0 | 6 |
| 3.0 | 7 |
| 4.0 | 8 |
| 5.0 | 9 |
| 6.0 | 10 |
| 7.0 | 11 |

接口说明

|      | 类型 | 描述 |
| :--- | :--- | :--- |
| 输入 | String   |   必须,平台的判断条件,如2=<TR<4 |
|      | Array    | 可选,依赖文件列表,规则同define接口定义的文件路径 |
|      | Function | 可选,当前条件下需要执行的脚本 |
| 输出 | Void     | 无 |

```javascript
NEJ.define([
    './hack.js'
],function(h){
    // 针对trident平台的处理逻辑
    NEJ.patch('TR',function(){
        // TODO
    });
    // 针对gecko平台的处理逻辑
    NEJ.patch('GR',[
        './hack.firefox.js'
    ],function(fh){
        // TODO
    });
    // 针对IE6平台的处理逻辑
    NEJ.patch('TR==2.0',['./hack.ie6.js']);
    // 针对IE7-IE9的处理逻辑
    NEJ.patch('3.0<=TR<=5.0',function(){
        // TODO
    });

    // 这里必须同hack.js文件的返回值一致
    return h;
});
```

### deps

通过配置来管理依赖,对于历史遗留项目或者使用了非AMD规范的第三方库如需引入依赖管理,可以通过此接口先配置依赖关系表,后期维护可直接使用依赖方式进行

开发阶段会全部依赖,发布只提取页面使用的脚本

接口说明

|      | 类型  | 描述 |
| :--- | :--- | :--- |
| 输入 | Array | 可选,依赖映射表 |
|      | Array | 可选,入口屏蔽文件列表 |
| 输出 | Void  | 无 |

假设系统依赖文件配置表为 deps.js, 文件内容如下

```javascript
NEJ.deps({
    '{pro}a.js':['{pro}b.js','{pro}c.js','{pro}d.js'],
    '{pro}b.js':['{pro}c.js','{pro}e.js'],
    '{pro}c.js':['{pro}d.js','{pro}e.js','{pro}a.js'],
    '{pro}d.js':['{pro}e.js'],
    '{pro}f.js':['{pro}a.js']
},[
    '{pro}f.js'
]);
```

则页面引入方式可用以下形式

```html
<script src="/path/to/nej/define.js?d=./deps.js&pro=./"></script>
<script src="./f.js"></script>
<script>
    NEJ.define([
        '{pro}a.js',
        '{pro}e.js'
    ],function(){
        log('app');
    });
</script>
```

## 循环依赖

当依赖链出现了环时我们就认为出现了循环依赖,依赖管理系统对于循环依赖的处理分两种情况

如下箭头方向表示文件的依赖,如 a.js 依赖 b.js

```
   a.js  ->  b.js  ->  a.js
```

* 强依赖:依赖文件之间的接口调用直接出现在文件定义函数中,避免代码出现强依赖,执行过程会出异常
* 弱依赖:依赖文件之间的接口调用出现在文件定义函数内部的某个接口中,允许出现弱依赖,可以正常处理

### 强依赖

在define接口中输入的执行函数里直接调用了依赖列表中的其他文件中的API的情况

a.js文件

```javascript
NEJ.define([
    './b.js'
],function(b,p){

    // 在此函数中直接调用了b的接口
    var result = b.api();

    p.api = function(){
        // TODO
        return 'aaaaa';
    };

    return p;
});
```

b.js文件

```javascript
NEJ.define([
    './a.js'
],function(a,p){

    // 在此函数中直接调用了a的接口
    var result = a.api();

    p.api = function(){
        // TODO
        return 'bbbbbb';
    };

    return p;
});
```

### 弱依赖

在define接口中输入的执行函数里没有直接调用依赖列表中的其他文件中的API,所有对其他文件的API的调用均在当前文件提供的API中调用的情况

a.js文件

```javascript
NEJ.define([
    './b.js'
],function(b,p){

    // 在其他API中调用了b的接口
    p.doSomething = function(){
        var result = b.api();
    };

    p.api = function(){
        // TODO
        return 'aaaaa';
    };

    return p;
});
```

b.js文件

```javascript
NEJ.define([
    './a.js'
],function(a,p){

    // 在其他API中调用了a的接口
    p.doSomething = function(){
        var result = b.api();
    };

    p.api = function(){
        // TODO
        return 'bbbbbb';
    };

    return p;
});
```


================================================
FILE: doc/DISPATCHER.md
================================================
# 模块调度系统

## 概述

随着WEB技术的发展,使用WEB技术构建富应用的场景越来越多,相比于纯Native技术,利用WEB技术构建富应用有其先天优势,如良好的跨平台特性、高效的构建及需求响应效率、更低的资源成本投入等,随着移动互联网的快速崛起、硬件设备的不断升级换代,WEB构建的应用体验将会越来越接近于纯Native的体验

使用WEB技术构建单页富应用在混合应用技术中使用的越来越多,此类应用复杂度要高于一般网站类型的WEB系统,采用模块化技术可用来将复杂系统拆分为若干简单子系统,同时所有子系统又需兼具低耦合,高可重组性,实际案例如:

* 移动混合应用(如 [网易云相册IPad版](http://photo.163.com/cloudphotos/)、[Lofter](http://www.lofter.com/app)等)
* 桌面混合应用(如 [网易云音乐PC版](http://music.163.com/#/download)、[网易闪电邮](http://flashmail.163.com/)等)

NEJ提供了一套模块调度系统 用于支持单页富应用的系统架构、模块拆分和重组、模块调度管理 等功能

## 模块

这里我们定义的模块是指从系统中拆分出来的可与用户进行交互完成一部分完整功能的独立单元

### 模块组成

因为这里描述的模块可独立与用户完成交互功能,因此模块会包含以下元素

* 样式:定义模块的效果
* 结构:定义模块的结构
* 逻辑:实现模块的功能

以上元素对于一个WEB系统开发者来说并不陌生,而我们只需要寻求一种形式将这些内容封装起来即可

### 模块封装

从模块的组成我们可以看到从系统中分离出来的模块可能会长成这个样子,比如module.html就是我们分离出来的一个模块,当然这里也可以用脚本文件封装,样式和结构采用注入形式,我们这里以html文件封装举例

```html
<!-- 模块样式 -->
<style>
    .m-mdl-1 .a{color:#aaa;}
    .m-mdl-1 .b{color:#bbb;}

    /* 此处省略若干内容 */
</style>

<!-- 模块结构 -->
<div class="m-mdl-1">
  <p class="a">aaaaaaaaaaaaaaaaaaa</p>
  <p class="b">bbbbbbbbbbbbbbbbbbb</p>

  <!-- 此处省略若干内容 -->
</div>

<!-- 模块逻辑 -->
<script>
    (function(){
        var a = 'aaa';
        var b = 'bbb';

        // 此处省略若干内容
    })();
</script>
```

而这个模块在用户需要时加载到客户端,并展现出来跟用户进行交互,完成功能;但是我们会发现如果系统预加载了此模块或者模块在parse时这些内容会被直接执行,而这个结果并不是我们需要的,因此我们需要将模块的各元素文本化处理,文本化处理有多种方式,如作为文本script、textarea等标签内容,因此module.html里的模块我们可以封装成如下样子,以textarea举例:

```html
<!-- 模块样式 -->
<textarea name="css">
    .m-mdl-1 .a{color:#aaa;}
    .m-mdl-1 .b{color:#bbb;}

    /* 此处省略若干内容 */
</textarea>

<!-- 模块结构 -->
<textarea name="html">
    <div class="m-mdl-1">
      <p class="a">aaaaaaaaaaaaaaaaaaa</p>
      <p class="b">bbbbbbbbbbbbbbbbbbb</p>

      <!-- 此处省略若干内容 -->
    </div>
</textarea>

<!-- 模块逻辑 -->
<textarea name="js">
    (function(){
        var a = 'aaa';
        var b = 'bbb';

        // 此处省略若干内容
    })();
</textarea>
```

## 管理依赖

从系统中拆分出来的模块之间是存在有一定关系的,如一个模块的呈现必须依赖另外一个模块的呈现,下面我们会以一个简单的例子来讲解模块之间的依赖管理,如下图是我们的一个单页富应用

![单页富应用范例](http://nej.netease.com/images/sample.gif)

从上图不难看出整个系统包含以下几部分内容:

* 日志管理

  * 日志:日志列表,可切换收件箱/草稿箱/回收站/标签
  * 标签:标签列表,可转至日志按标签查看列表

* 博客设置

  * 账号管理

    * 基本资料:用户基本资料设置表单
    * 个人经历:个人经历填写表单

  * 权限设置:权限设置表单

而这些模块之间的层级关系则如下所示

![模块层级结构图](http://img0.ph.126.net/7ixXaTU-uFtbe0pKCWCgPA==/6597270977286264717.png)

针对交互式系统的这种层级架构典型的模式可以参阅[PAC(Presentation-Abstraction-Control)模式](http://en.wikipedia.org/wiki/Presentation%E2%80%93abstraction%E2%80%93control) 或者 [HMVC(Hierarchical model–view–controller)模式](http://en.wikipedia.org/wiki/Hierarchical_model%E2%80%93view%E2%80%93controller)

然而在WEB交互式系统的实践过程中我们发现这种模式会存在一些缺陷:

* 由于每个父模块自己维护了所有的子模块,因此父子模块之间耦合性过强,父模块必须耦合所有子模块
* 由于模块之间不能越级调用,因此子模块需要其他模块协助时必须层层向上传递事件,如果层级过深则会影响到系统效率
* 模块的增删等变化导致的变更涉及的影响较大,删除中间节点上的模块可能导致相邻的若干模块的变更
* 多人协作开发系统时存在依赖关系的模块会导致开发人员之间的紧密耦合

在这里我们给出了一种基于模块标识的依赖管理配置方案,可以彻底的将模块进行解耦,每个模块可以独立完整的完成自己的交互功能,而系统的整合则可以通过配置的方式灵活的重组各模块,模块的增删操作只需修改配置即可完成,而无需影响到具体业务逻辑

下文我们会通过以上例子来讲解此方案的原理和实际操作方式

### 模块标识

因为本方案会基于模块标识做配置,因此在介绍方案之前我们先介绍一下模块标识,这里我们给模块标识取名为**UMI**(Uniform Module Identifier)统一模块标识,下文简称UMI,遵循以下规则约定

* 格式同URI的Path部分,如 /m/m0/
* 必须以“/”符开始
* 私有模块必须以“/?”开始
* 承载模块的依赖关系,如 /m/m0/ 和 /m/m1/ 表明这两个标识对应模块的父模块标识均为 /m

每个UMI均可唯一标识一个模块及模块在系统中的依赖关系,在模块章节我们介绍了一个模块可以用一个html进行封装,因此我们可以得到以下结果

![UMI与模块地址映射关系图](http://img0.ph.126.net/DCSPjxzVgjlgc8B983YSSg==/2673167853921558567.png)

每个UMI均可映射到一个模块实现文件,这样我们就可以将模块从具体实现中解耦出来,对模块的增删修改操作只需调整UMI和模块文件的映射关系即可,而无需涉及具体业务逻辑的修改

### 模块依赖

在解决了模块与实现分离的问题后,我们接下来需要将层级式的模块扁平化来解耦模块之间的依赖关系

回到前面的例子,模块之间的层级关系如下图所示

![模块层级关系图](http://img1.ph.126.net/NFbfUjokCb1KsI8tCj04xw==/6608203421400487879.jpg)

如果我们将图中的依赖关系进行抽象分离后可以发现所有的模块即可呈现扁平的状态

![模块关系分离图](http://img1.ph.126.net/XByNuSHTyJ_OzmAFIcqGKQ==/6608480498329578217.png)

而对于模块之前的依赖关系的管理在所有系统中都是一致的,但是每个模块的具体功能实现是由系统来决定的,不同的系统是截然不同的,因此本方案提供的解决方案主要是用来维护模块之间的依赖关系的

从上图我们可以比较清楚的看到模块之间的依赖关系呈现树状结构,因此我们会以树的结构来组织维护模块之间的依赖关系,我们称之为依赖关系树,而当我们将这棵树上的任意节点与根节点之间的路径用“/”分隔序列化后发现刚好与我们提供的UMI是匹配的,因此组成系统的模块的UMI可以跟依赖关系树的节点一一对应起来,如下图所示

![依赖关系树节点序列化成UMI](http://img2.ph.126.net/cu36voh3bRXFx530aW6nkg==/6608921402492317376.png)

在模块标识章节我们介绍了UMI与模块封装文件可以相互映射,因此依赖关系树上的节点可以直接与模块的实现文件做一一对应,如下图所示

![依赖关系树映射模块实现文件](http://img1.ph.126.net/6Pfkz1_4dd32jnL60QQ1Kw==/6608598146073751639.png)

至此,我们将垂直层级依赖的模块通过依赖关系树分解成了无任何关系的扁平模块结构

### 模块组合

模块只需要有个呈现容器即可渲染出来,因此模块如果需要能够做任意组合,只需将模块分成两种类型:提供容器的模块和使用容器的模块即可,当然一个模块可同时兼具提供容器和使用容器的功能,提供容器的模块和使用容器的模块可任意组合

![模块组合](http://img2.ph.126.net/D446SK2cBk_aGZFLVRwIew==/3669307796500606643.png)

对于模块组合的配置代码范例

```javascript
'/m/blog/list/':{
    module:'module/layout/blog.list/index.html',
    composite:{
        box:'/?/blog/box/',
        tag:'/?/blog/tag/',
        list:'/?/blog/list/',
        clazz:'/?/blog/class/'
    }
}
```

## 调度策略

在将模块扁平化后,各模块就可以安排给不同的开发人员进行功能实现和测试了,各模块完成后根据依赖关系树进行系统整合,完成系统整合后各模块会遵循一定的调度策略进行调度

### 模块状态

根据模块调度的阶段划分,模块的状态可以分为以下四个阶段:

* 模块构建:构建模块结构
* 模块显示:将模块渲染到指定的容器中
* 模块刷新:根据外界输入的参数信息获取数据并展示(这里主要做数据处理)
* 模块隐藏:模块放至内存中,回收由刷新阶段产生的额外结构

调度策略主要控制模块在这几个阶段之间的转换规则

### 模块显示

当用户请求显示一个模块时各模块会遵循以下步骤进行调度,假设请求显示 /m/blog/list/ 模块

![显示调度策略](http://img1.ph.126.net/ip4S9-mTCAH943VGwXZ1OQ==/6619563575537201227.png)

1. 检查目标节点到根节点路径上注册的模块,如果注册的是模块的实现文件地址,则请求载入模块实现文件
2. 如果节点所在的模块的所有祖先节点已显示,则当前模块可被显示出来,否则等待祖先模块的显示调度
3. 模块载入后根据第二步骤原则尝试调度目标模块的显示

### 模块切换

当用户从一个模块切换到另外一个模块时各模块遵循以下步骤调度,假设从 /m/blog/list/ 切换到 /m/setting/account/edu/ 模块

![模块切换](http://img0.ph.126.net/ca9IdYk2ezLL-zN8vRqI4Q==/6619574570653479153.png)

1. 查找源模块与目标模块的公共父节点

   ![查找公共模块](http://img2.ph.126.net/G36CYcFsck1w8Uf0k7oOIw==/6608672912864439374.png)

2. 从源节点到公共节点之间的模块调度隐藏操作

   ![隐藏模块](http://img0.ph.126.net/kCydlPd-q7yUIQ2rc65Eyg==/6608818048399307052.png)

3. 从根节点到公共节点之间的模块调度刷新操作

   ![刷新模块](http://img2.ph.126.net/sN7NPW5etJ0FVjv-TyBL9w==/6608677310910950450.png)

4. 从公共节点到目标节点之间的模块调度显示操作

   ![显示模块](http://img1.ph.126.net/DwnLyinZy9ZG8IX_uwOmsg==/6599306173308730570.png)

## 消息通道

大部分时候我们不需要模块之前的消息通信,实践中也存在一些特殊情况会需要模块之前的消息通信,这里提供两种方式的消息通讯

* 点对点的消息:一个模块发送消息时明确指定目标模块的UMI
* 观察订阅消息:一个模块可以对外申明发布了什么样的消息,有需要的模块可以订阅该模块UMI上的消息

## 实例解析

这部分我们用上面的具体实例讲解如何使用NEJ的模块调度系统来拆分一个复杂系统、开发测试模块、整合系统

### 系统分解

#### 绘制层级关系图

当我们拿到一个复杂系统时根据交互稿可以绘制出组成系统的模块的层级关系图,并确定系统对外可访问的模块

![模块层级关系图](http://img1.ph.126.net/UmNMLNzb-Vjdtv8skT8kqg==/6608742182096989774.png)

#### 抽象依赖关系树

从模块的层级关系图中我们可以非常方便的抽象出模块的依赖关系树

![抽象依赖关系树](http://img1.ph.126.net/BK5_LhDhh01addpDJuY4Yw==/6619331578583739475.png)

将抽象出来的依赖关系树根据UMI规则进行格式化,格式化的主要操作包括

* 增加一个名称为“/”的根结点(也可将“m”结点改为“/”)
* 每个结点增加“/”的子节点作为默认节点

![格式化依赖关系树](http://img2.ph.126.net/5b3ByaubY0XFOlnU1PZHAw==/6619382156118617138.png)

至此输出的依赖关系树,具有以下特性:

* 任何一个结点(除根结点外)到根结点路径上的结点名称用“/”分隔组合起来即为结点的UMI值,如list结点的UMI值为/m/blog/list
* 任何结点上的模块都依赖于他祖先结点(注册有模块)上的模块存在,如blog结点和list结点均注册有模块,则list结点上的模块显示必须以blog结点上的模块的显示为先决条件

#### 确定对外模块注册节点

五个对外可访问的模块:日志、标签、基本资料、个人经历、权限设置,在依赖关系树中找到合适的结点(叶子结点,层级关系树在依赖关系树中对应的结点或“/”结点)来注册对外可访问的模块

![对外模块注册节点](http://img0.ph.126.net/vC28JzPSddgzZdpEAeBZ2w==/6619485510211628655.png)

#### 确定布局模块注册节点

从可访问模块注册的结点往根结点遍历,凡碰到两模块交叉的结点即为布局模块注册结点,系统所需的组件相关的模块可注册到根结点,这样任何模块使用的时候都可以保证这些组件已经被载入

![布局模块注册节点](http://img1.ph.126.net/Jjp0GCDfh2eXvaxJvhiCeQ==/6619215030351195733.png)

#### 映射模块功能

原则:结点的公共父结点实现结点上注册的模块的公共功能

举例:blog结点和setting结点的公共父结点为m结点,则我们可以通过切换blog模块和setting模块识别不变的功能即为m模块实现的功能,同理其他模块

![功能映射](http://img2.ph.126.net/64xhHIfGy4qdQKSV5Bym-g==/2679360303409193237.png)

#### 分解复杂模块

进一步分解复杂模块,一般需要分解的模块包括:

* 可共用模块,比如日志列表,可以在日志管理页面呈现,也可以在弹层中显示
* 逻辑上无必然联系的模块,如日志模块中日志列表与右侧的按标签查看的标签列表之间没有必然的联系,任何一个模块的移除或添加都不会影响到另外一个模块的业务逻辑

![分解复杂模块](http://img1.ph.126.net/wRM1uAxQDRFhfAzlNw8b5Q==/6599274287471525959.png)

至此我们可以得到两棵系统分解后的依赖关系树

对外模块依赖关系树

![对外模块依赖关系树](http://img2.ph.126.net/6yAKfH-sST3-RGc0xIxU2g==/6608788361585356880.png)

私有模块依赖关系树

![私有模块依赖关系树](http://img1.ph.126.net/KgimU46Zn4ZnUtxiBrXdKw==/6608191326772584502.png)

#### 绘制模块功能规范表

本例中为了说明分解过程将所有可分解的模块都做了分解,实际项目看具体情况,比如这里的/m模块组合的/?/tab/模块的功能可以直接在/m模块中实现,而不需要新建一个/?/tab/模块来实现这个功能

规范表范例如下所示

![功能规范表](http://img1.ph.126.net/e_0ZFwbefuXT5bZ6bq17Rg==/1496320976294650210.png)

### 构建目录

#### 项目目录

项目目录的构建如下图所示

![项目目录](http://img0.ph.126.net/7mfjeInfGILw-tWQznxxLA==/6608777366469080397.png)

各目录说明

```
webroot                    项目前端开发相关目录
   |- res                  静态资源文件目录,打包时可配置使用到该目录下的静态资源带版本信息
   |- src                  前端源码目录,最终发布时该目录不会部署到线上
       |- html
            |- module      单页面模块目录,系统所有模块的实现均在此目录下
            |- app.html    单页面入口文件
```

#### 模块单元目录

根据模块封装规则一个模块单元由以下几部分组成:

* 模块测试:模块实现的功能可以通过模块测试页面独立进行测试
* 模块结构:模块所涉及的结构分解出来的若干模板集合
* 模块逻辑:根据模块规范实现的模块业务逻辑,从模块基类继承
* 模块样式:模块特有的样式,一般情况下这部分样式可以直接在css目录下实现

结构范例如下所示

![模块单元目录](http://img0.ph.126.net/o6dTV8RpqAtS63zxdz8sEg==/6608917004445806742.png)

至此我们可以得到所有模块的目录结构如下所示

![模块目录结构](http://img0.ph.126.net/2cMlaKsMBzOfAPPdH9IzkA==/6608797157678379135.png)

### 模块实现

#### 结构

这里我们假设系统的静态页面已经做完,这里的模块实现只是在原有结构的基础上进行结构分解和业务逻辑的实现,结构部分内容主要将模块相关的静态结构拆分成若干NEJ的模板,注意:

* 模板中的外联资源如css,js文件地址如果使用的是相对路径则均相对于模块的html文件路径
* 模板集合中的外联资源必须使用@TEMPLATE标记标识,这个在后面打包发布章节会详细介绍

NEJ模板说明

![NEJ模板](http://img2.ph.126.net/W_RhwYyx4kbPPGMpcVoUwQ==/6608414527633039667.png)

模块结构举例

```html
<meta charset="utf-8"/>

<textarea name="txt" id="m-ifrm-module">
  <div class="n-login">
    <div class="iner j-flag">
      <span class="cls j-flag">×</span>
      <span class="min j-flag">-</span>
    </div>
    <div class="cnt j-cnt"></div>
  </div>
</textarea>


<textarea name="css" data-src="./index.css"></textarea>
<textarea name="js" data-src="./index.js"></textarea>

```

#### 逻辑

依赖util/dispatcher/module模块,从\_$$ModuleAbstract扩展一个项目的模块基类,完成项目中模块特有属性、行为的抽象

```javascript
/*
 * ------------------------------------------
 * 项目模块基类实现文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
NEJ.define([
    'base/klass',
    'util/dispatcher/module'
],function(_k,_t,_p){
    // variable declaration
    var _pro;
    /**
     * 项目模块基类对象
     * @class   {_$$Module}
     * @extends {_$$ModuleAbstract}
     * @param   {Object}  可选配置参数,已处理参数列表如下所示
     */
    _p._$$Module = _k._$klass();
    _pro = _p._$$Module._$extend(_t._$$ModuleAbstract);
    /**
     * 操作
     * @param  {Object}
     * @return {Void}
     */
    _pro.__doSomething = function(_args){
        // TODO
    };

    // TODO

    return _p;
});
```

根据模块状态的划分,我们在实现一个模块时需要实现以下几个接口

![模块各阶段接口](http://img0.ph.126.net/8XyKVwG3dzG0dK59qteZFw==/6619467918025585071.png)

各阶段对应的接口:

* 构建 - \_\_doBuild:构建模块结构,缓存模块需要使用的节点,初始化组合控件的配置参数
* 显示 - \_\_onShow:将模块放置到指定的容器中,分配组合控件,添加相关事件,执行\_\_onRefresh的业务逻辑
* 刷新 - \_\_onRefresh:根据外界输入的参数信息获取数据并展示(这里主要做数据处理)
* 隐藏 - \_\_onHide:模块放至内存中,回收在\_\_onShow中分配的组合控件和添加的事件,回收\_\_onRefresh中产生的视图(这里尽量保证执行完成后恢复到\_\_doBuild后的状态)

具体模块实现举例

```javascript
/*
 * ------------------------------------------
 * 项目模块实现文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
NEJ.define([
    'base/klass',
    'util/dispatcher/module',
    '/path/to/project/module.js'
],function(_k,_e,_t,_p){
    // variable declaration
    var _pro;
    /**
     * 项目模块对象
     * @class   {_$$ModuleDemo}
     * @extends {_$$Module}
     * @param   {Object} 可选配置参数
     */
    _p._$$ModuleDemo = _k._$klass();
    _pro = _p._$$ModuleDemo._$extend(_t._$$Module);
    /**
     * 构建模块,主要处理以下业务逻辑
     * - 构建模块结构
     * - 缓存后续需要使用的节点
     * - 初始化需要使用的组件的配置信息
     * @return {Void}
     */
    _pro.__doBuild = function(){
        this.__super();
        // TODO
    };
    /**
     * 显示模块,主要处理以下业务逻辑
     * - 添加事件
     * - 分配组件
     * - 处理输入信息
     * @param  {Object} 输入参数
     * @return {Void}
     */
    _pro.__onShow = function(_options){
        this.__super(_options);
        // TODO
    };
    /**
     * 刷新模块,主要处理以下业务逻辑
     * - 分配组件,分配之前需验证
     * - 处理输入信息
     * - 同步状态
     * - 载入数据
     * @return {Void}
     */
    _pro.__onRefresh = function(_options){
        this.__super(_options);
        // TODO
    };
    /**
     * 隐藏模块,主要处理以下业务逻辑
     * - 回收事件
     * - 回收组件
     * - 尽量保证恢复到构建时的状态
     * @return {Void}
     */
    _pro.__onHide = function(){
        this.__super();
        // TODO
    };
    // notify dispatcher
    _e._$regist(
        'umi_or_alias',
        _p._$$ModuleDemo
    );

    return _p;
});
```
#### 消息

##### 点对点消息

模块可以通过\_\_doSendMessage接口向指定UMI的模块发送消息,也可以通过实现\_\_onMessage接口来接收其他模块发给他的消息

发送消息

```javascript
_pro.__doSomething = function(){

    // TODO

    this.__doSendMessage(
        '/m/setting/account/',{
            a:'aaaaaa',
            b:'bbbbbbbbb'
        }
    );
};
```

接收消息

```javascript
_pro.__onMessage = function(_event){
    // _event.from 消息来源
    // _event.data 消息数据,这里可能是 {a:'aaaaaa',b:'bbbbbbbbb'}

    // TODO
};
```

##### 发布订阅消息

发布消息

```javascript
_pro.__doSomething = function(){

    // TODO

    this.__doPublishMessage(
        'onok',{
            a:'aaaaaa',
            b:'bbbbbbbb'
        }
    );
};
```

订阅消息

```javascript
_pro.__doBuild = function(){

    // TODO

    this.__doSubscribeMessage(
        '/m/message/account/','onok',
        this.__onMessageReceive._$bind(this)
    );
};
```

#### 自测

创建html页面,使用模板引入模块实现文件

```html
<!-- template box -->
<div id="template-box" style="display:none;">
  <textarea name="html" data-src="../index.html"></textarea>
</div>
```

模块放至document.mbody指定的容器中

```javascript
NEJ.define([
    'util/dispatcher/test'
],function(_e){
    document.mbody = 'module-id-0';
    // test module
    _e._$testByTemplate('template-box');
});
```

### 系统整合

#### 映射依赖关系树

系统整合时,我们只需要将依赖关系树中需要注册模块的节点同模块实现文件进行映射即可

对外模块整合

![对外模块整合](http://img2.ph.126.net/CQe6y5Bdgkl4bA5VsSIwzA==/6608893914701623469.png)

私有模块整合

![私有模块整合](http://img0.ph.126.net/upKrOu1fEmK_Q6x2jc-ibA==/6619430534630239416.png)

#### 提取系统配置信息

规则配置举例

```javascript
  rules:{
      rewrite:{
          '404':'/m/blog/list/',
          '/m/blog/list/':'/m/blog/',
          '/m/setting/account/':'/m/setting/'
      },
      title:{
          '/m/blog/tag/':'日志标签',
          '/m/blog/list/':'日志列表',
          '/m/setting/permission/':'权限管理',
          '/m/setting/account/':'基本资料',
          '/m/setting/account/edu/':'教育经历'
      },
      alias:{
          'system-tab':'/?/tab/',
          'blog-tab':'/?/blog/tab/',
          'blog-list-box':'/?/blog/box/',
          'blog-list-tag':'/?/blog/tag/',
          'blog-list-class':'/?/blog/class/',
          'blog-list':'/?/blog/list/',
          'setting-tab':'/?/setting/tab/',
          'setting-account-tab':'/?/setting/account/tab/',

          'layout-system':'/m',
          'layout-blog':'/m/blog',
          'layout-blog-list':'/m/blog/list/',
          'layout-setting':'/m/setting',
          'layout-setting-account':'/m/setting/account',

          'blog-tag':'/m/blog/tag/',
          'setting-edu':'/m/setting/account/edu/',
          'setting-profile':'/m/setting/account/',
          'setting-permission':'/m/setting/permission/'
      }
  }
```

模块配置举例

```javascript
  modules:{
      '/?/tab/':'module/tab/index.html',
      '/?/blog/tab/':'module/blog/tab/index.html',
      '/?/blog/box/':'module/blog/list.box/index.html',
      '/?/blog/tag/':'module/blog/list.tag/index.html',
      '/?/blog/class/':'module/blog/list.class/index.html',
      '/?/blog/list/':'module/blog/list/index.html',
      '/?/setting/tab/':'module/setting/tab/index.html',
      '/?/setting/account/tab/':'module/setting/account.tab/index.html',

      '/m':{
          module:'module/layout/system/index.html',
          composite:{
              tab:'/?/tab/'
          }
      },
      '/m/blog':{
          module:'module/layout/blog/index.html',
          composite:{
              tab:'/?/blog/tab/'
          }
      },
      '/m/blog/list/':{
          module:'module/layout/blog.list/index.html',
          composite:{
              box:'/?/blog/box/',
              tag:'/?/blog/tag/',
              list:'/?/blog/list/',
              clazz:'/?/blog/class/'
          }
      },
      '/m/blog/tag/':'module/blog/tag/index.html',

      '/m/setting':{
          module:'module/layout/setting/index.html',
          composite:{
              tab:'/?/setting/tab/'
          }
      },
      '/m/setting/account':{
          module:'module/layout/setting.account/index.html',
          composite:{
              tab:'/?/setting/account/tab/'
          }
      },
      '/m/setting/account/':'module/setting/profile/index.html',
      '/m/setting/account/edu/':'module/setting/edu/index.html',
      '/m/setting/permission/':'module/setting/permission/index.html'
  }
```

#### 模块组合

模块通过\_\_export属性开放组合模块的容器,\_\_export中的parent为子模块的容器节点,顶层模块(如 “/m”)可以通过重写\_\_doParseParent来明确指定应用所在容器

```javascript
_pro.__doBuild = function(){
    this.__body = _e._$html2node(
        _e._$getTextTemplate('module-id-l2')
    );
    // 0 - box select
    // 1 - class list box
    // 2 - tag list box
    // 3 - sub module box
    var _list = _e._$getByClassName(this.__body,'j-flag');
    this.__export = {
        box:_list[0],
        clazz:_list[1],
        tag:_list[2],
        list:_list[3],
        parent:_list[3]
    };
};
```

通过composite配置模块组合

```javascript
'/m/blog/list/':{
    module:'module/layout/blog.list/index.html',
    composite:{
        box:'/?/blog/box/',
        tag:'/?/blog/tag/',
        list:'/?/blog/list/',
        clazz:'/?/blog/class/'
    }
}
```

模块组合时可以指定组合模块的处理状态
* onshow  - 这里配置的组合模块仅在模块显示时组合,后续的模块refresh操作不会导致组合模块的refresh,适合于模块在显示后不会随外界输入变化而变化的模块
* onrefresh  -  这里配置的模块在模块显示时组合,后续如果模块refresh时也会跟随做refresh操作,适用于组合的模块需要与外部输入同步的模块
* 不指定onshow或者onrefresh的模块等价于onrefresh配置的模块

```javascript
composite:{
    onshow:{
        // 模块onshow时组合
        // 组合的模块在模块onrefresh时不会刷新
    },
    onrefresh{
        // 模块onshow时组合
        // 组合的模块在模块onrefresh时也同时会刷新
    }
    // 这里配置的组合模块等价于onrefresh中配置的模块
}
```

#### 启动应用

根据配置启动应用

```javascript
NEJ.define([
    'util/dispatcher/dispatcher'
],function(_e){
    _e._$startup({
        // 规则配置
        rules:{
            rewrite:{
                // 重写规则配置
            },
            title:{
                // 标题配置
            },
            alias:{
                // 别名配置
                // 建议模块实现文件中的注册采用这里配置的别名
            }
        },
        // 模块配置
        modules:{
            // 模块UMI对应实现文件的映射表
            // 同时完成模块的组合
        }
    });
});
```

### 打包发布

打包发布内容详见[NEJ工具集](https://github.com/genify/toolkit)相关文档

## 系统变更

当系统需求变化而进行模块变更我们只需要开发新的模块或删除模块配置即可

### 新增模块

如果新增的模块功能在系统中已经实现,则只需修改配置即可,如上例中我们需要在将日志管理下的标签模块在博客设置中也加一份,访问路径为/m/setting/tag/

![新增模块](http://img0.ph.126.net/s4pv6FZmPal8Jx0m0eNltA==/6619471216560468933.png)

修改规则配置

```javascript
rules:{
    // ...
    alias:{
        // ...
        'blog-tag':['/m/blog/tag/','/m/setting/tag/']
    }
}
```

修改模块配置

```javascript
modules:{
    // ...
    '/m/setting/tag/':'module/blog/tag/index.html'
}
```

如果要在/?/setting/tab模块的结构模板中增加一个标签即可

```html
<textarea name="txt" id="module-id-8">
  <div class="ma-t w-tab f-cb">
    <a class="itm fl" href="#/setting/account/" data-id="/setting/account/">账号管理</a>
    <a class="itm fl" href="#/setting/permission/" data-id="/setting/permission/">权限设置</a>
    <a class="itm fl" href="#/setting/tag/" data-id="/setting/tag/">日志标签</a>
  </div>
</textarea>
```

### 删除模块

将退化的模块从系统中删除只需要将模块对应的UMI配置从模块配置中删除即可,而无需修改具体业务逻辑





================================================
FILE: doc/FAQ.md
================================================


================================================
FILE: doc/MESSAGE.md
================================================
# 跨域窗体消息系统

## 概述

由于同源策略的限制,非同域的窗体之间不能直接进行数据通讯或共享,因此我们引入这部分内容来分析解决非同域及同域的窗体之间的数据通讯或共享问题。

这里的消息通讯是指应用在客户端跨窗体之间的消息交互,可以保证在不会被恶意攻击的前提下进行跨窗体的数据共享方式,这里不会涉及到客户端与服务器端的信息交互。

关于这部分对应的W3C规范的详细介绍可参阅[HTML5 Web Message](http://www.w3.org/TR/webmessaging/)

## 实现分析

消息通讯主要包含两个过程:

* 收消息:文档通过监听当前窗体的onmessage事件来接收来自其他窗体的消息
* 发消息:文档可以通过给定的接口向指定的窗体发送消息,包括跨域的窗体

原理示意图如下所示

![web message原理](http://img1.ph.126.net/YJDCgyyYN9oEYj6M6nd4Wg==/6608228710167581530.png)

这里我们可以简单理解为各个窗体之间有一个消息通道,对于消息通道的关系我们定义为以下两种类型:

* 直接父子关系:消息通道所在的窗体为父子关系
* 兄弟及祖孙关系:消息通道所在的窗体为兄弟关系,或者父窗体和子窗体的若干层子窗体关系

对于全浏览器平台兼容的消息机制实现一般采用以下两种方式

* HTML5 Web Message
* window.name代理

## HTML5 Web Message

### 原理

对于高版本实现了符合W3C [HTML5 Web Message](http://www.w3.org/TR/webmessaging/) 规范的浏览器,我们可以直接使用该规范提供的API来实现跨域的窗体之间的消息通信

#### MessageEvent

MessageEvent定义

![message event](http://img2.ph.126.net/YAxyMByji3SBIkgz563_1g==/2209297092302616725.png)

data

* 消息内容,支持多种格式
* IE8-9只支持字符串

origin

* 消息来源,如http://a.b.com:8080
* 从安全角度出发在收到消息时务必做来源验证

source

* 消息来源的窗体
* 回复消息,如event.source.postMessage

ports

* 消息通道,见[Channel](#Channel)部分说明
* 发送消息,如event.ports[0].postMessage
* webkit(chrome)/presto(opera)/trident(IE10+)支持

#### 发消息

原理图示

![send message](http://img2.ph.126.net/EKXmXzABYnLfQATvn-GbQA==/1396397359562519963.png)

发送消息使用规范定义的window.postMessage接口,接口定义如下

|      | 类型 | 描述 |
| :--- | :--- | :--- |
| 输入 | Variable | 发送的数据,可以是基本类型,也可以是File、Blob等对象,注IE8-9支持传递字符串格式的数据 |
|      | String   | 目标可接收消息的源信息,如果目标的源不是该参数指定的源则忽略此消息,如果所有源均可接收消息则可以传“*”,如果只允许同域的窗体接收消息则可以传“/” |
|      | Array    | 可选,消息通道对象列表,主要用来代理非直接父子关系的窗体之间的消息通讯 |
| 输出 | Void     | 无 |

代码举例

```html
<iframe src="http://a.b.com:1144/frame-a.html" name="A"></iframe>
```

```javascript
// 向名称为A的iframe发送消息,消息内容为字符串 connect
window.frames.A.postMessage(
    'connect',
    'http://a.b.com:1144'
);
```

#### 收消息

接收消息使用规范定义的window.onmessage事件,接收到的事件对象见[MessageEvent](#MessageEvent)定义

代码举例

```javascript
// http://a.b.com:1144/frame-a.html可以通过window.onmessage事件来监听其他窗口发过来的消息
window.addEventListener(
    'message',function(event){
        // check origin
        if (!isAllowed(event.origin))
            return;
        // check data format
        if (!isFormatOK(event.data))
            return;
        // TODO something
        ...
        // reply to source
        event.source.postMessage('message',event.origin);
    },false
);
```

#### Channel

前面我们通过postMessage接口和onmessage事件实现了父子窗体之间的消息通讯,接下来我们需要完成兄弟关系的窗体之间的消息通讯

![message between sibling](http://img2.ph.126.net/-eHlgNW2xZrps3HETzHvjQ==/1414411758071900501.png)

这里如果window2要向window1发送消息,因为window2不能直接拿到window1的窗体对象,因此无法直接通过postMessage接口来向window1发送消息,一种折中的方式就是通过父窗体将消息做一次转发,window2先给消息parent,然后由parent将消息转交给window1,如下图所示

![message by parent proxy](http://img1.ph.126.net/Xjfu7BcqwtGbhkbbUDMFMA==/6619527291653512726.png)

但是这样我们会发现消息的路径比较长,因此效率比较低,W3C针对此类消息提供了MessageChannel机制来完成消息通讯

一个MessageChannel包含两个端口,每个端口可以独立的完成消息的收发功能

![message channel abstract](http://img2.ph.126.net/kv-J9xwXLKRUYweaUKkqDw==/2210985942163057055.png)

MessageChannel定义

![message channel](http://img2.ph.126.net/MV9A26usRaaTBOT7keKDVw==/4796333603250057446.png)

因此兄弟关系的窗体之间的消息通讯机制可抽象为如下图所示

![message between sibling](http://img2.ph.126.net/jjYkSurWb66RN3ALMVktOQ==/3017974700392032857.png)

下面通过代码例子来解析整个流程

假设一个页面有两个跨域的iframe窗体,需要实现通过MessageChannel从A窗体向B窗体发送消息的功能

```html
<iframe src="http://a.b.com:1144/frame-a.html" name="A"></iframe>
<iframe src="http://d.e.com:1122/frame-b.html" name="B"></iframe>
```

A窗体中,我们需要先构建一个MessageChannel,在当前窗体上保持port1端口,通过port1端口进行消息的收发功能,然后将port2端口传递给parent窗体,由parent窗体将port2端口转交给B窗体

```javascript
var channel = new MessageChannel();
channel.port1.onmessage = function(event){
    // check origin from frame-b.html
    if (event.origin!='http://d.e.com:1122')
        return;
    log('receive message from d.e.com and say: '+event.data);
    // reply message
    channel.port1.postMessage('hello B!',event.origin);
};
// build connect by parent
parent.postMessage('connect','http://c.d.com:1100',[channel.port2]);
```

parent窗体需要将A窗口传过来的端口转交给B窗体,因此在parent窗体上需要做以下转发功能

```javascript
window.addEventListener(
    'message',function(event){
        // from frame-a to build connect
        if (event.origin!='http://a.b.com:1144')
            return;
        // proxy port to frame-b
        if (event.ports.length>0){
            window.frames.B.postMessage(
                'connect',
                'http://d.e.com:1122',
                [event.ports[0]]
            );
        }
    },false
);
```

B窗体在接收到parent传过来的port2端口后,保持port2端口,通过port2端口完成跟A窗体的消息收发功能

```javascript
window.addEventListener(
    'message',function(event){
        // check build channel message from parent
        if (event.origin!='http://c.d.com:1100')
            return;
        // build channel
        if (event.ports.length>0){
            event.ports[0].onmessage = function(ev){
                // check message from frame-a.html
                if (ev.origin!='http://a.b.com:1144')
                    return;
                log('receive message from a.b.com and say: '+ev.data);
            };
            // send message to frame-a.html
            event.ports[0].postMessage('hello A!',ev.origin);
        }
    },false
);
```

至此A窗体和B窗体之间就建立了一条直接的MessageChannel,后续所有消息通过通过该消息通道直接进行通讯,而不需要通过parent做消息中转

## window.name代理

对于低版本浏览器主要针对Trident引擎的浏览器,如IE6-7版本,对于这类浏览器本身对Web Message规范的实现不完善,因此采用window.name作为代理来实现消息的传递。

### 原理

这里我们主要利用Trident引擎下跨域窗体可设置window.name的特性来实现消息的传递,具体实现原理示意图如下所示

![message by window.name](http://img1.ph.126.net/NM6uHYXVUEU06McBcIR8tw==/6608700400654849159.png)

假设上图Window1需要传递消息至Window2中,则消息的传递步骤如下:

1.  Window1的消息发送器按照指定协议拼装消息
2.  Window1的消息发送器将拼装好的消息字符串设置到Window2的window.name属性上
3.  Window2起定时器轮询window.name的变化情况
4.  Window2发现window.name中Window1设置的消息串,按照指定协议解码
5.  Window2触发window上的onmessage事件通知上层应用收到消息

反之亦然

### 协议

这里的协议主要指window.name上设置的消息串的格式

* 必须以字符串 MSG| 作为起始,且必须大写字符
* 参数以键值对方式传入,键与值之间用 = 连接,所有键值均做encodeURIComponent编码,键值对之间以 | 字符分隔,如 a=b|b=a%26b
* 将以上结果做escape后设置到目标window的name属性上,如MSG%7Cdata%3D%257B%2522url%2522%253A%2522http%253A%252F%252Fa.b.com

传递的参数信息主要包括

| 参数名称 | 参数描述 |
| :---     | :---     |
| origin   | 目标接收消息的源信息 |
| data     | 传递的消息数据,序列化为JSON字符串 |
| ref      | 当前窗体的访问地址 |
| self     | 当前窗体名称,默认为_parent |

### 缺陷

由于浏览器的限制本解决方案会存在以下一些问题

1.  可能会有消息丢失

    由于使用的是定时器轮询window.name的变化,而window.name为所有其他窗体共享的资源,但是其他窗体仅有设置权限而没有读取权限,因此无法保证window.name的消息在读取前被程序中其他代码覆盖,如果出现这种情况之前的消息无法到达当前窗体中。

2.  没能真正意义上保证origin的限制条件

    由于无法读取一个窗体的location信息,因此对origin的验证是在接收到消息后由框架额外做的封装,因此从真正意义上来说这个消息还是已经到达了当前窗体中,因此可能会存在信息的泄漏情况

综上所述,对于低版本浏览器建议尽量避免传递一些敏感信息,同时系统也尽量避免强依赖消息传递机制来实现系统的重要功能。

## NEJ封装

[NEJ](https://github.com/NetEaseWD/NEJ) Web前端开发框架根据以上原理对消息通讯这部分做了封装,为上层应用提供统一的API,主要实现模块为 util/ajax/message

### 发送消息

NEJ封装的发送消息接口为_$postMessage,接口说明如下

|      | 类型 |   描述 |
| :--- | :--- | :--- |
| 输入 | String/Window | 目标window对象或者Frame的name,或者字符串如\_top、\_parent、\_self |
|      | Object        | 消息配置信息 |
| 输出 | Void          | 无 |

消息配置信息包括

| 名称 | 类型 | 描述 |
| :--- | :--- | :--- |
| data   | Variable  | 消息内容 |
| origin | String    | 目标Origin,只有指定的页面可以收到消息,默认为所有源可接收,如http://a.b.com |
| source | String    | 当前窗体标识,除非你非常确定当前窗体的标识是什么,否则请采用自动识别 |

代码举例

```html
<!-- 注意需要通过source进行双向交互的frame节点必须设置id属性作为标识 -->
<iframe id="targetFrame" src="http://a.b.com/a.html"></iframe>
```

```javacript
NEJ.define([
    'util/ajax/message'
],function(_j){
    // top页面代码
    // 发送消息至 http://c.d.com 的页面
    _j._$postMessage('targetFrame',{
        data:'hello c.d.com',
        origin:'http://c.d.com'
    });

    // 发送消息至 http://a.b.com 的页面
    _j._$postMessage('targetFrame',{
        data:'hello a.b.com'
    });
});
```

### 接收消息

跟W3C规范一致,NEJ同样提供window.onmessage事件来接收消息,注这里的onmessage事件必须通过_$addEvent接口添加,接收到的消息格式遵循W3C中对于[MessageEvent](#MessageEvent)的规范

代码举例

```javacript
NEJ.define([
    'base/event'
    'util/ajax/message'
],function(_v,_j){
    // 添加消息监测事件
    _v._$addEvent(
        window,'message',function(_event){
            // 必须先验证消息来源_event.origin是否你允许的域
             if (!_isAllow(_event.origin))
                return;

            // 处理_event.data中的消息内容
            // TODO something

            // 回复消息,使用_event.source
            _j._$postMessage(_event.source,{
                data:'hello!',
                origin:_event.origin
            });
        }
    );
});
```



================================================
FILE: doc/PLATFORM.md
================================================
# 平台适配系统

## 概述

随着WEB技术的发展,越来越多的平台开始使用WEB技术来构建系统,NEJ框架做为一套跨平台的技术解决方案框架集成了基于AOP思想的平台适配策略,该策略具有以下特性:

* 屏蔽平台差异
* 松耦合性(通过配置应用可以灵活的增删平台支持)
* 可扩展性(框架可以快速增加对新平台的支持)
* 可维护性(直接针对平台进行维护)
* 工具支持(通过NEJ提供的打包工具可按需定制平台输出)

## 平台分类

NEJ框架根据WEB应用所在容器的差异将平台分为浏览器平台和混合应用平台两大类,各分类的详细说明见下文所述

### 浏览器平台

#### 按引擎划分

浏览器平台按照主流引擎可以划分为以下几类:

![按引擎划分平台](http://img0.ph.126.net/gmJEDK7pAdfE8nH4VQexkA==/6608273790144412074.png)

| 引擎 | 说明 |
| :--- | :--- |
| Trident | 由微软研发的排版引擎,代表浏览器有Internet Explorer |
| Webkit  | 由Apple、Google、Adobe等公司推动的开源的排版引擎,代表浏览器有Apple Safari、Google Chrome |
| Gecko   | 由Mozilla基金会支持的开源排版引擎,代表浏览器有Mozilla Firefox |
| Presto  | 由Opera Software研发的商用排版引擎,代表浏览器有Opera,由于Opera从15以后就开始采用新的Blink引擎,因此Presto也将逐步淡出我们的目标平台对象 |
| Blink   | 由Google和Opera Software基于Webkit引擎研发的排版引擎,代表浏览器有Chrome 28+、Opera 15+ |

根据对引擎分析及各引擎的流行度我们将默认目标平台锁定为Trident、Webkit、Gecko三大平台,根据平台适配策略我们后续可以快速的进行平台扩展,各引擎对应的平台依赖配置信息参阅《[NEJ依赖管理系统](./DEPENDENCY.md)》

| 引擎    | 依赖配置参数p值 |
| :---    | :--- |
| Trident | td |
| Webkit  | wk |
| Gecko   | gk |

#### 按功能划分

各引擎的浏览器版本根据对标准、规范的支持程度进行划分可分为以下几类:

![按功能划分平台](http://img1.ph.126.net/PELRvZvw_Gx7DTN3v7hUFQ==/6608921402492080696.png)

由于目前国内基于Trident的Internet Explorer浏览器还占有大量的市场份额,包括低版本的Internet Explorer浏览器,因此我们将浏览器分成三个等级

| 标准性 | 说明 |
| :--- | :--- |
| 差 | 主要针对低版本的Trident引擎(如IE6浏览器)平台,这部分平台对规范和标准的支持程度比较差,在适配时需要做大量额外的hack工作来实现相应的功能,因此如果产品的目标平台定位需要支持此平台则会有一定的性能损耗 |
| 中 | 主要针对中间版本的Trident引擎(如IE7-9浏览器)平台,这部分平台对规范和标准有一定的支持,但是也存在若干功能需要做额外的hack工作来实现 |
| 好 | 主要针对对规范、标准支持比较好的平台,按照标准实现的功能无需做额外的hack工作,因此如果产品的目标平台定位为此平台将取得比较好的用户体验和性能,如移动产品、混合应用等 |

因此根据对引擎和浏览器份额的分析,NEJ针对Trident引擎默认支持导出的目标平台可分为三个等级,这里涉及的依赖管理相关信息参阅《[NEJ依赖管理系统](./DEPENDENCY.md)》

| 等级 | 依赖配置参数p值 | 说明 |
| :--- | :--- | :--- |
| 差 |   td |    支持IE6的全平台等级 |
| 中 |   td-0 |  支持>=IE7的等级 |
| 好 |   td-1 |  支持IE10+的等级 |

### 混合应用平台

根据混合应用的宿主平台的差异我们将混合应用的目标平台分为以下几类:

![混合平台划分](http://img1.ph.126.net/1VV98yGBpj3E2KPaa9M3jg==/6608853232771155778.png)

| 宿主     | 说明 |
| :---     | :--- |
| Android  | Android系统的混合应用,浏览器引擎会自动适配至Webkit |
| IOS      | IOS系统的混合应用,浏览器引擎会自动适配至Webkit |
| WinPhone | Windows Phone系统的混合应用,浏览器引擎会自动适配至Trident |
| PC       | 桌面应用,采用CEF做为容器,浏览器引擎会自动适配至Webkit |

NEJ对于混合应用的配置提供以上四种平台的支持,这里涉及的依赖管理相关信息参阅《[NEJ依赖管理系统](./DEPENDENCY.md)》

| 平台     | 依赖配置参数p值 |
| :---     | :--- |
| Android  | android |
| IOS      | ios |
| WinPhone | win |
| PC-CEF   | cef |

## 平台配置

### 配置类型

平台配置信息分两类基本配置:补丁配置和混合配置,因为混合模式下使用的浏览器引擎固定,因此当配置中出现混合类型的配置时忽略补丁配置的值。

如果在引入依赖定义库时未指定平台信息则表示系统需对全平台浏览器支持。

#### 补丁配置

主要用来修正浏览器平台对接口及控件的支持,按照目前浏览器引擎划分,参数值由一个或者多个平台标识组成,多个平台用“|”分隔,标识支持如下所示:

| 标识 | 说明 |
| :--- | :--- |
| gk   | 以gecko为核心的浏览器平台,如firefox等 |
| wk   | 以webkit为核心的浏览器平台,如chrome、safari等 |
| td   | 以trident为核心的浏览器平台,如IE、360、maxthon等 |
| td-0 | 以trident为核心的浏览器平台,且引擎内核版本大于等于3.0,即IE>=7 |
| td-1 | 以trident为核心的浏览器平台,且引擎内核版本大于等于7.0,即IE>=10 |

![补丁配置举例](http://img2.ph.126.net/0Lice657qJuv-bQ5EMTjBA==/6608225411632792322.png)

#### 混合配置

主要用于混合开发模式下对native接口的适配,按照native平台划分,参数值由一个标识组成,多个标识则以识别的第一个标识为准,标识支持如下所示:

| 标识    | 说明 |
| :---    | :--- |
| cef     | 基于cef框架混合应用,主要针对桌面应用 |
| ios     | ios平台混合应用,如iphone应用、ipod应用、ipad应用等 |
| win     | windows phone平台混合应用 |
| android | android平台混合应用 |

对于此类值的配置自动生成的native值为{lib}native/目录下的具体配置目录。

![混合配置举例](http://img1.ph.126.net/QZTOhRvFyNtocnXu0jHdsQ==/6608507986120036696.png)

### 配置方式

平台参数在开发及打包过程中都会使用,NEJ框架支持平台参数的配置通过define.js路径上查询串中的p参数输入,同时也支持在打包工具配置文件中配置。

#### 查询参数配置

通过引入的define.js路径上的p查询参数配置,可以按照浏览器引擎配置

```html
<script src="/lib/define.js?p=wk|gk|td"></script>
```

也可以按照混合应用的宿主平台进行配置

```html
<script src="/lib/define.js?p=android"></script>
```

#### 打包参数配置

除了通过查询配置参数进行平台配置外还可以使用打包参数进行平台配置,打包时打包参数的配置优先级大于查询参数,对于非NEJ文件依赖管理的系统因为没有引入define.js,所以只能采用打包参数配置的形式

```
 # NEJ平台适配参数,等价于define.js?p=wk|td这里p的配置
 # 优先级比p参数高,配置规则同p
 NEJ_PLATFORM = wk|td
```

## 实现机制

### 平台分离策略

#### AOP思想

AOP(Aspect-Oriented Programming):面向切面的编程范式,其核心思想是将横切关注点从主关注点中分离出来,因此特定领域的问题代码可以从标准业务逻辑中分离出来,从而使得主业务逻辑和领域性业务逻辑之间不会存在任何耦合性。

NEJ框架利用AOP思想来实现平台的适配策略,结合不同的平台实现逻辑,我们可以认为对于使用规范、标准来实现业务逻辑的部分为我们的主关注点,而不同平台可以做为若干的切面关注点进行封装,各平台只需关注自己平台下对标准的修正逻辑即可,因此可以通过增加删平台修正逻辑来实现对不同平台的适配。

实现时我们首先提取标准业务逻辑,然后各平台根据实际情况实现对业务逻辑的修正

![AOP策略](http://img2.ph.126.net/27YJvuHJvtvBFv4FoYX7QA==/6608822446445580374.png)

* 标准业务逻辑:主关注点,这里主要是使用根据W3C、ES标准来实现的业务逻辑
* 前置平台修正逻辑:领域特定关注点,主要是根据平台特性对标准在该平台下的修正,修正逻辑会先于标准逻辑执行
* 后置平台修正逻辑:同前置平台修正逻辑,也是领域特定关注点,修正逻辑会在标准逻辑执行后再执行

根据此思路我们对比以下两段代码:

代码一:目前常用的平台检查方式

```javascript
function doSomething(){
    if(isTrident){
        // TODO trident implement
    }else if(isWebkit){
        // TODO webkit implement
    }else if(isGecko){
        // TODO gecko implement
    }else if(isPresto){
        // TODO presto implement
    }else{
        // TODO w3c implement
    }
}
```

此方式对所有平台的修正逻辑均在主逻辑中实现,存在以下弊端:

* 对平台特有的修正逻辑耦合在主逻辑中,平台特有的更新必然引起主逻辑的更新
* 对于新增或删除平台的支持必须修改到主业务逻辑
* 无法分离不必要的平台修正,比如基于webkit引擎的移动平台应用

代码二:NEJ框架的平台适配方式

```javascript
function doSomething(){
    // TODO w3c implement
}

// trident implement
doSomething = doSomething._$aop(
    function(_event){
        // TODO trident implement
    }
);
// … …

doSomething(1,2,3);
```

对比代码一,我们可以发现NEJ框架中的接口适配方式分离了标准业务逻辑和平台特有业务逻辑,而是否增加平台特有业务逻辑并不会影响主业务逻辑的执行,因此我们可以从中得到以下好处:

* 主逻辑和平台特有逻辑无耦合性,可随意分离、合并
* 对于新增平台适配只需新加平台特有逻辑即可,而无需影响到主业务逻辑
* 可通过配置有选择性的导出平台特有业务逻辑

#### _$aop

NEJ框架在Function的原型链上增加了\_$aop接口的支持来做平台适配

|      | 类型 |   描述 |
| :--- | :--- | :--- |
| 输入 | Function |   平台前置业务逻辑 |
|      | Function | 平台后置业务逻辑 |
| 输出 | Function |   封装了平台前置、后置业务逻辑的执行函数 |
| 描述 |          | AOP适配逻辑封装整合 |

对于平台前置、后置业务逻辑的执行函数接受一个输入参数,该参数包含以下信息

| 参数名称 | 类型     | 描述 |
| :---     | :---     | :--- |
| args     | Array    | 输入参数列表,调整参数值将影响后续阶段的输入 |
| value    | Variable | 返回值,后续阶段可取到此值 |
| stopped  | Boolean  | 是否继续执行后续阶段的业务逻辑 |

代码举例:

平台差异API的提取

```javascript
function doSomething(a,b,c){
    // TODO do something
};

doSomething(1,2,3);
```

平台适配逻辑

```javascript
doSomething = doSomething._$aop(
    function(_event){
        // _event.args -> [a,b,c]
        // _event.stopped = true;
        // _event.value = 'aaaaaaa';

        // TODO before doSomething
    },
    function(_event){
        // _event.args -> [a,b,c]
        // _event.value = 'bbbbbb';

        // TODO after doSomething
    }
);

doSomething(1,2,3);
```

### 按需适配策略

前面NEJ提供了将平台适配的主业务逻辑和平台业务逻辑的分离机制,除此之外还需要提供一种机制能够让平台按需适配,并能通过平台配置的形式按需导出

#### platform

控件依赖补丁名称为“platform”,只用于文件依赖,使用“{platform}xxx.js”来表示控件依赖的补丁文件,会解析为依赖xxx.js和xxx.patch.js两个文件。xxx.js为W3C/ES规范实现方式,提供所有标准平台支持的公用部分,xxx.patch.js通过NEJ.patch接口提供不同平台对这些接口的特有实现逻辑

一个典型的适配控件结构如下图所示

![适配控件结构](http://img1.ph.126.net/shaaEm3oj13JZcdMF9iHTQ==/6608208918958379461.png)

这里的widget.js是控件业务逻辑实现文件,在此控件的实现中会依赖到存在平台差异的API,其依赖代码如下所示

```javascript
NEJ.define([
    'util/event',
    '{platform}api.js'
],function(t,h,p){

    // TODO

});
```

这里对 {platform}api.js 的处理方式如下图所示,这里的./相对于当前的代码文件即widget.js文件所在的目录

![platform](http://img2.ph.126.net/ygcxx2DAXf5YbpQ8No7SWg==/6608689405538619068.png)

这里的api.js文件为需平台适配API的标准实现逻辑,而api.patch.js文件则利用NEJ.patch接口对各平台做按需适配逻辑,同时打包时也根据NEJ.patch接口中对平台的条件识别做按需输出,由于api.patch.js文件最终会按需输出,因此在此文件中除了使用NEJ.patch做平台适配逻辑外不允许包括其它业务逻辑

#### NEJ.patch

NEJ框架中的平台按需适配采用NEJ.patch接口来实现,由于打包发布后NEJ.patch相关的接口会被分离出来不会发布上线,因此仅允许在patch文件中调用此接口,平台引擎标识说明如下

| 标识  | 说明 |
| :---  | :--- |
| T     | Trident引擎,如IE |
| W     | Webkit引擎,如chrome |
| G     | Gecko引擎,如firefox |

内置的Trident引擎版本对应的IE版本关系

| Trident版本 | IE版本 |
| :---  | :--- |
| 2.0 | 6 |
| 3.0 | 7 |
| 4.0 | 8 |
| 5.0 | 9 |
| 6.0 | 10 |
| 7.0 | 11 |

接口说明

|      | 类型 | 描述 |
| :--- | :--- | :--- |
| 输入 | String   |   必须,平台的判断条件,如2=<TR<4 |
|      | Array    | 可选,依赖文件列表,规则同define接口定义的文件路径 |
|      | Function | 可选,当前条件下需要执行的脚本 |
| 输出 | Void     | 无 |

```javascript

// 此文件只能定义NEJ.patch不可执行其他业务逻辑
// 打包输出时仅根据平台配置输出所需处理逻辑

NEJ.define([
    './hack.js'
],function(h){
    // 针对trident平台的处理逻辑
    NEJ.patch('TR',function(){
        // TODO
    });
    // 针对gecko平台的处理逻辑
    NEJ.patch('GR',[
        './hack.firefox.js'
    ],function(fh){
        // TODO
    });
    // 针对IE6平台的处理逻辑
    NEJ.patch('TR==2.0',['./hack.ie6.js']);
    // 针对IE7-IE9的处理逻辑
    NEJ.patch('3.0<=TR<=5.0',function(){
        // TODO
    });

    // 这里必须同hack.js文件的返回值一致
    return h;
});
```

## 平台变更

当平台发生变更时我们可以快速进行扩充或缩减

### 平台扩充

当有新平台需要作为系统目标平台时,我们只需要做以下工作:

* 增加平台配置识别符,如nxw
* 识别该平台与标准存在的差异,增加平台特有业务逻辑至patch
* 系统对平台配置部分增加新添的识别符,如

    原平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk|gk|td"></script>
    ```
    新增平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk|gk|td|nxw"></script>
    ```

即可完成对平台的扩充,而不会影响到原有的业务逻辑

### 平台缩减

当系统适配的目标平台由于种种原因逐步退出历史舞台时,我们的系统也需要将该平台的冗余代码从系统中剔除,我们只需要做以下工作:

* 系统对平台配置部分删除要剔除的平台标识,如

    原平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk|gk|td"></script>
    ```
    缩减后平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk"></script>
    ```

即可完成对平台的缩减,而无需修改任何业务逻辑

## 使用规范

### 目录规范

对于存在平台适配的API或者控件实现时需要将平台适配相关的代码放在platform目录下,适配相关文件包含两部分:标准逻辑实现文件和适配逻辑实现文件,其中适配逻辑实现文件命名必须为标准实现文件名加”.patch”做为后缀,如下图所示标准逻辑实现文件w1.js对应的平台适配逻辑实现文件名必须为w1.patch.js

![目录规范](http://img0.ph.126.net/XDO5ArV_6or1s2vUQEiENw==/6608191326772334325.png)

这里对于控件的实现文件路径和命名没有强制规定,因为{platform}相对于当前路径搜索适配文件,所以对于层级目录的控件实现文件中如果需要依赖平台文件则可以使用类似../../{platform}w1.js的形式引入

### 编码规范

编码规范这里主要针对patch文件有以下约束,其他遵循NEJ编码规范即可

* patch文件只允许调用NEJ.patch接口做平台适配相关业务逻辑
* patch文件必须显式的依赖标准逻辑实现文件
* 不同平台依赖的外部文件在实现时必须处理好平台识别
* patch文件返回结果必须同标准逻辑实现文件一致

## 使用范例

### 步骤一

构建控件目录,可以使用[NEJ工具集](https://github.com/genify/toolkit)的nej-widget指令生成

![控件目录](http://img2.ph.126.net/SSxNFLzX53Nb8r5DLuukQg==/6608848834724645147.png)

### 步骤二

实现控件主业务逻辑(widget.js),这里依赖适配文件hack.js

```javascript
NEJ.define([
    '{platform}hack.js'
],function(){
    // TODO
    console.log('widget ok');
});
```

### 步骤三

实现适配接口的标准业务逻辑(hack.js),这里依赖平台解析文件base/platform

```javascript
NEJ.define([
    'base/platform'
],function(_h){

    // common api for w3c or es
    _h.__doSomething = function(){
        // TODO
    };

    console.log('from common hack file');
});
```

### 步骤四

实现适配业务逻辑(hack.patch.js),这里需要显式指定标准逻辑实现文件hack.js,对于不同平台复杂的适配实现可以采用外链依赖的方式实现

```javascript
// 此文件只能定义NEJ.patch不可执行其他业务逻辑
// 打包输出时仅根据平台配置输出所需处理逻辑
NEJ.define([
    './hack.js'
],function(_h){
    // 针对trident平台的处理逻辑
    NEJ.patch('TR',function(){
        // TODO
        console.log('from inline ie');
    });

    // 针对webkit平台的处理逻辑
    NEJ.patch('WR',[
        './hack.chrome.js'
    ],function(_hc){
        // TODO
        console.log('from inline chrome');
    });

    // 针对gecko平台的处理逻辑
    NEJ.patch('GR',[
        './hack.firefox.js'
    ],function(_hf){
        // TODO
        console.log('from inline firefox');
    });

    // 针对IE6平台的处理逻辑
    NEJ.patch('TR==2.0',['./hack.ie6.js']);

    // 针对IE7-IE9的处理逻辑
    NEJ.patch('3.0<=TR<=5.0',function(){
        // TODO
        console.log('from inline ie7-ie9');
    });

    // 这里必须返回hack.js返回的结果
    return _h;
});
```

### 步骤五

外链平台适配文件的实现(hack.ie6.js)

```javascript
NEJ.define([
    'base/platform',
    './hack.js'
],function(_p,_h){

    // 外链适配依赖做好平台识别
    if (_p._$NOT_PATCH.trident0) return;

    // patch for ie6
    _h.__doSomething =
    _h.__doSomething._$aop(
        function(_event){
            // TODO
        }
    );

    console.log('from ie6 dep file');
});
```



================================================
FILE: doc/TEMPLATE.md
================================================
# 模板系统

## 概述

模板系统主要用来分离视图与数据,用以生成特定格式的文档,可以提升开发效率,良好的设计也可以使得代码重用变得更加容易,主要特点包括:

* 分离代码(业务逻辑代码与视图代码)
* 数据分离(动态数据与静态数据)
* 代码单元共享(代码共享)

本文主要介绍NEJ框架的模板系统及其使用,模板系统提供两大类模板的支持:基本类型和资源类型,模版系统的实现模块包括util/template/tpl和util/template/jst

## 模板结构

NEJ模板系统可以直接用字符串作为模板,也可以使用TEXTAREA标签封装模板,使用TEXTAREA标签封装模版的形式如下

![模板类型](http://img2.ph.126.net/W_RhwYyx4kbPPGMpcVoUwQ==/6608414527633039667.png)

### 基本类型

结构范例

```html
<textarea name="txt" id="txt-template-1">
  <div>
    <p>aaaaaaaaaaaaaa</p>
    <!-- content here -->
  </div>
</textarea>
```

#### name

标识模板类型,主要包括txt/jst/ntp三种,每种类型的详细说明见模板类型章节

#### id

模板标识,后续可以用模板接口输入该ID取得模板的内容

### 资源类型

结构范例

```html
<textarea name="css" data-src="./a.css" data-version="20140901">
  .a{color:#000;}
  .b{color:#ddd;}
</textarea>
```

#### name

标识模板类型,主要包括css/js/html/res四种类型

#### data-src

标识模板资源地址,多个地址用“,”分隔,以“./”,“../”开始的相对路径相对于当前文件所在的目录

#### data-version

资源版本信息

## 模板类型

模板系统提供两类模板,根据实际需求,每类模板又做了细化分类

### 基本类型

基本类型模板主要包括 txt/jst/ntp 三类模板

#### txt

txt模板提供了基本的文本缓存功能, 模板标示符为txt,开发人员可以使用这个模板来缓存html结构

如果脚本里已经有了模版内容则可以通过_$addTextTemplate接口添加到缓存中

代码举例

```javascript
NEJ.define([
    'util/template/tpl'
],function(_t){
    // 模版添加到缓存池中
    _t._$addTextTemplate('txt-template-1','\
        <div>\
          <p>aaaaaaaaaaaaaa</p>\
          <!-- content here -->\
        </div>\
    ');

    // TODO
});
```

如果模版使用textarea标签封装的话采用以下形式

代码举例

```html
<textarea name="txt" id="txt-template-1">
  <div>
    <p>aaaaaaaaaaaaaa</p>
    <!-- content here -->
  </div>
</textarea>
```

此类模板后续可以使用_$getTextTemplate接口获取

```javascript
NEJ.define([
    'util/template/tpl'
],function(_t){
    // 解析模版,支持textarea的批量解析
    _t._$parseTemplate('txt-template-1');

    // 根据模板ID取模板内容
    // 返回字符串类型的模板内容
    var _text = _t._$getTextTemplate('txt-template-1');

    // TODO
});
```

#### ntp

节点模版,主要用于UI控件中对复杂结构的缓存和重用,见[ITEM控件](#ITEM控件)

如果是字符串的模版,则可以通过_$addNodeTemplate接口添加至缓存

代码举例

```javascript
NEJ.define([
    'util/template/tpl'
],function(_t){
    // 模版添加到缓存池中
    _t._$addNodeTemplate('ntp-template-1','\
        <div>\
          <p>aaaaaaaaaaaaaa</p>\
          <!-- content here -->\
        </div>\
    ');

    // TODO
});
```

如果模版使用textarea标签封装的话采用以下形式

代码举例

```html
<textarea name="ntp" id="ntp-template-1">
  <div>
    <p>aaaaaaaaaaaaaa</p>
    <!-- content here -->
  </div>
</textarea>
```

此类模板后续可以使用_$getNodeTemplate接口获取

```javascript
NEJ.define([
    'util/template/tpl'
],function(_t){
    // 解析模版,支持textarea的批量解析
    _t._$parseTemplate('ntp-template-1');

    // 根据模板ID取模板内容
    // 返回模板对应的DOM树结构
    var _node = _t._$getNodeTemplate('ntp-template-1');

    // TODO
});
```

#### jst

符合[JST语法](#JST语法)规则的模版,类似服务器端模版如freemarker、verlocity等

如果模版是字符串,则可以通过_$add接口添加到缓存

代码举例

```javascript
NEJ.define([
    'util/template/jst'
],function(_t){
    // 添加JST模板缓存
    _t._$add('\
        <table class="w-table">\
          <thead>\
            <tr><th>序号</th><th>姓名</th><th>性别</th></tr>\
          </thead>\
          {if !defined("workers")}\
          <tr><td colspan="3">数据加载失败,请稍后再试!</td></tr>\
          {elseif workers&&workers.length}\
            {list workers as x}\
              <tr{if x_index==x_length-1} class="last"{/if}>\
                <td>${x_index+1}</td>\
                <td>${x.name}</td>\
                <td>{if x.gender==1}男{else}女{/if}</td>\
              </tr>\
            {/list}\
          {else}\
          <tr><td colspan="3">没有工人!</td></tr>\
          {/if}\
        </table>\
    ');
})
```

如果模版使用textarea标签封装的话采用以下形式

代码举例

```html
<textarea name="jst" id="jst-template-1">
    <table class="w-table">
      <thead>
        <tr><th>序号</th><th>姓名</th><th>性别</th></tr>
      </thead>\
      {if !defined("workers")}
      <tr><td colspan="3">数据加载失败,请稍后再试!</td></tr>
      {elseif workers&&workers.length}
        {list workers as x}
          <tr{if x_index==x_length-1} class="last"{/if}>
            <td>${x_index+1}</td>
            <td>${x.name}</td>
            <td>{if x.gender==1}男{else}女{/if}</td>
          </tr>
        {/list}
      {else}
      <tr><td colspan="3">没有工人!</td></tr>
      {/if}
    </table>
</textarea>
```

后续可以使用_$get接口获取整合数据的结果

代码举例

```javascript
NEJ.define([
    'util/template/jst'
],function(_t){
    // 添加模版缓存
    // 也可以用_$parseTemplate接口批量添加
    _t._$add('jst-template-1');

    // 根据模板ID取模板内容
    // 返回整合数据后的html代码
    var _html = _t._$get('jst-template-1',{
        workers:[
            {name:'abc',gender:1},
            {name:'def',gender:1},
            {name:'ghi'}
        ]
    });

    // TODO
});
```

### 资源类型

资源类型模板又分为 css/js/html/res 四类,资源类模板只能用TEXTAREA标签封装

#### css

样式资源模版,可以采用内联也可以采用外联方式载入

代码举例

```html
<div id="template-box">
    <!-- 纯内联样式 -->
    <textarea name="css">
      .a{color:#aaa;}
      .b{color:#bbb;}
    </textarea>

    <!-- 纯外联 -->
    <textarea name="css" data-src="./a.css,./b.css" data-version="v1"></textarea>

    <!-- 复合内联外联 -->
    <textarea name="css" data-src="./a.css,./b.css" data-version="v1">
      .a{color:#aaa;}
      .b{color:#bbb;}
    </textarea>
</div>
```

对于内外联复合的样式模板,解析成样式时,内联的样式在外联的样式的后面

系统初始化时使用_$parseTemplate接口解析模版

代码举例

```javascript
NEJ.define([
    'util/template/tpl'
],function(_t){
    // 激活样式模版
    _t._$parseTemplate('template-box');

    // TODO
});
```

#### js

脚本资源模版,可以采用内联也可以采用外联方式载入

代码举例

```html
<div id="template-box">
    <!-- 纯内联样式 -->
    <textarea name="js">
      var a = 'aaaa';
      var b = 'bbbb';
    </textarea>

    <!-- 纯外联 -->
    <textarea name="js" data-src="./a.js,./b.js" data-version="v1"></textarea>

    <!-- 复合内联外联 -->
    <textarea name="js" data-src="./a.css,./b.css" data-version="v1">
      var a = 'aaaa';
      var b = 'bbbb';
    </textarea>
</div>
```

对于内外联复合的脚本模板激活时,内联的脚本在外联的脚本载入完成后执行

系统初始化时使用_$parseTemplate接口解析模版

代码举例

```javascript
NEJ.define([
    'util/template/tpl'
],function(_t){
    // 激活脚本
    _t._$parseTemplate('template-box');

    // TODO
});
```

#### html

外联模版集合,载入的结构会递归进行调用_$parseTemplate接口解析模版,项目中一般用于载入通用控件结构,html模版的data-src不支持多个模版用“,”分隔的形式

代码举例

```html
<div id="template-box">
    <textarea name="html" data-src="./a.html" data-version="v1"></textarea>
    <textarea name="html" data-src="./b.html" data-version="v1"></textarea>
</div>
```

系统初始化时使用_$parseTemplate接口解析模版,如果要确保代码中能够使用外联的模版,则需要在document的ontemplateready事件中处理

代码举例

```javascript
NEJ.define([
    'base/event',
    'util/template/tpl'
],function(_v,_t){
    // 载入外联模版集合
    _t._$parseTemplate('template-box');

    _v._$addEvent(
        document,'templateready',function(){
            // 这里可以保证外联的模板可用

            // TODO
        }
    );

    // TODO
});
```

#### res

外联文本资源,载入后作为txt类型的模版使用,需要指定id

代码举例

```html
<div id="template-box">
    <textarea name="txt" id="txt-0" data-src="./a.html" data-version="v1"></textarea>
    <textarea name="txt" id="txt-1" data-src="./b.html" data-version="v1"></textarea>
</div>
```

后续通过_$getTextTemplate接口使用,如果要确保代码中能够使用外联的模版,则需要在document的ontemplateready事件中处理

代码举例

```javascript
NEJ.define([
    'util/template/tpl'
],function(_t){
    // 解析模版,支持textarea的批量解析
    _t._$parseTemplate('template-box');

    _v._$addEvent(
        document,'templateready',function(){
            // 这里可以保证外联的模板可用

            // 根据模板ID取模板内容
            // 返回字符串类型的模板内容
            var _text0 = _t._$getTextTemplate('txt-0');
            var _text1 = _t._$getTextTemplate('txt-1');

            // TODO
        }
    );

    // TODO
});
```

## JST语法

### 表达式

#### ${}

描述:求值表达式,表达式中不可以包含 “{”或者“}”

语法:

```html
${expr}

${expr|modifier}

${expr|modifier1|modifier2|...|modifierN}

${expr|modifier1:argExpr1_1}

${expr|modifier1:argExpr1_1,argExpr1_2,...,argExpr1_N}

${expr|modifier1:argExpr1_1|...|modifierN:argExprN_1,argExprN_2,...,argExprN_M}
```

范例:

```html
${customer.firstName}

${customer.firstName|capitalize}

${customer.firstName|default:"no name"|capitalize}

${article.getCreationDate|default:new Date()|toCalendarControl:"YYYY.MM.DD",true,"creation Date"}

${(lastQuarter.calcRevenue() - fixedCosts) / 10000}
```

#### ${% %}

描述:求值表达式,表达式中可以包含 “{”或者“}”

语法:

```html
${% expr %}
```

范例:

```html
${% emitLink("Solution and Products", {color: "red", blink: false}) %}
```

### 语句

#### list break

描述:遍历数组

语法1:

```html
{list seq as varName}
    ...
{break}
    ...
{/list}
```

范例1:

```html
{list ["aaa", "bbbb", "ccccc"] as x}
  ${x_index}/${x_length}:${x}<br/>
{/list}
```

备注:

* x_index为内置变量,值为循环的索引值。
* x_length为内置变量,值为列表长度, 上例中值为3。

语法2:

```html
{list from..to as varName}
    ...
{/list}
```

备注:循环时包含from和to值

范例2:

```html
{list 2..10 as x}
    ${x_index}/${x_length}:${x}<br/>
{/list}
```

备注:

* x_index为内置变量,值为循环的索引值。
* x_length为内置变量,值为列表长度, 上例中值为9。

#### for forelse

描述:遍历HASH表

语法:

```html
{for varName in hash}
    ...
{forelse}
    ...
{/for}
```

注:forelse 子语句为可选

范例:

```html
{for x in {a:"aaa", b:"bbbb", c:"ccccc"}}
    ${x_key} - ${x}<br/>
{forelse}
    no pro
{/for}
```

注:x_key为内置变量,值为当前项的键值。

#### if elseif else

描述:条件控制语句

语法:

```html
{if expr}
    ...
{elseif expr}
    ...
{else}
    ...
{/if}
```

注:elseif、else 子语句为可选

范例:

```html
{if gender == 1}
    男
{elseif gender == 0}
    女
{else}
    春哥
{/if}
```

#### var

描述:变量定义

语法:

```html
{var varName}

{var varName = expr}
```

范例:

```html
{var test = "sssssss"}
```

#### macro

描述:宏定义

语法:

```html
{macro macroName(arg1, arg2, ... argN)}
    ... body of the macro ...
{/macro}
```

范例:

```html
{macro htmlList(dataList, optionalListType)}
    {var listType = optionalListType != null ? optionalListType : "ul"}
    <${listType}>
        {for item in dataList}
            <li>${item}</li>
        {/for}
    </${listType}>
{/macro}
```

调用宏:

```html
${htmlList(["首页", "日志","相册", "关于我"])}
```

输出:

```html
<ul>
    <li>首页</li>
    <li>日志</li>
    <li>相册</li>
    <li>关于我</li>
</ul>
```

#### cdata

描述:文本块,内容不做语法解析

语法:

```html
{cdata}
    ...no parsed text ...
{/cdata}
```

或

```html
{cdata EOF}
    ...no parsed text ...
EOF
```

范例:

```html
{cdata}
    ${customer.firstName}${customer.lastName}
{/cdata}
```

或

```html
{cdata END_OF_CDATA_SECTION}
    ${customer.firstName}${customer.lastName}
END_OF_CDATA_SECTION
```

输出:${customer.firstName}${customer.lastName}

#### minify

描述:压缩文本内容,内容不做语法解析

语法:

```html
{minify}
    ...multi-line text which will be stripped of line-breaks...
{/minify}
```

或

```html
{minify EOF}
    ...multi-line text which will be stripped of line-breaks...
EOF
```

范例:

```html
{minify}
    no parsed
    text
    and
    merge
    one
    line
{/minify}
```

或

```html
{minify EOF}
    no parsed
    text
    and
    merge
    one
    line
EOF
```

输出:no parsed text and merge one line

#### eval

描述:执行javascript语句,不做语法解析

语法:

```html
{eval}
    ...javascript statement...
{/eval}
```

或

```html
{eval EOF}
    ...javascript statement...
EOF
```

范例:

```html
{eval}
    var a = "aaaa";
    alert(a);
    function b(arg){
        alert(arg);
    }
{/eval}
```

或

```html
{eval EOF}
    var a = "aaaa";
    alert(a);
    function b(arg){
        alert(arg);
    }
EOF
```

### 扩展

#### rand

描述:随机一个指定长度的纯数字的串

语法:

```html
${number_expr|rand}
```

范例:

```html
${10|rand}
```

输出:3456785438

#### escape

描述:编码字符串

语法:

```html
${expr|escape}
```

范例:

```html
${"<div>1234<a href="#">163</a></div>"|escape}
```

输出:&amp;lt;div&amp;gt;1234&amp;lt;a href="#"&amp;gt;163&amp;lt;/a&amp;gt;&amp;lt;/div&amp;gt;

#### format

描述:格式化日期

语法:

```html
${data_expr|format:format_expr}
```

范例:

```html
${new Date()|format:"yyyy-MM-dd HH:mm:ss"}
```

输出:2012-06-13 16:30:55

#### default

描述:指定默认值

语法:

```html
${expr|default:default_expr}
```

范例:

```html
${null|default:"default value"}
```

输出:default value

注:当expr为undefiend,null,false,0或者空字符串时取默认值

## ITEM控件

ITEM控件提供了结构+逻辑的缓存功能,适合于列表项带复杂逻辑的模版,一般使用ntp模版来封装结构,列表类的ITEM基类抽象在ui/item/list模块中实现,因为ITEM控件也是UI控件,所以遵循[UI控件](./WIDGET.md)的规则

如下图所示的评论列表中,每一项评论又具有回复列表、回复和删除功能,我们可以将这种结构做成ITEM模版

![评论列表](http://img0.ph.126.net/EiEyOdGSqY0PASizUZMynA==/6608813650352806767.png)

ITEM控件的目录结构

```
  comment
     | - comment.css    评论项样式
     | - comment.html   评论项结构
     | - comment.js     评论项逻辑
```

comment.html文件使用ntp类型的模版做结构

```html
<div class="m-cmt">
  <div class="fce"><img class="j-flag"/></div>
  <div class="box">
    <div class="ttl j-flag"><!-- 用户占位 --></div>
    <div class="cnt j-flag"><!-- 内容占位 --></div>
    <div class="act j-flag">
      <a href="#" data-action="reply">回复</a>
      <a href="#" data-action="delete">删除</a>
    </div>
  </div>
</div>
```

comment.js文件继承ui/item/list模块的\_$$ListItem类进行扩展

```javascript
NEJ.define([
    'base/klass',
    'base/element',
    'base/event',
    'ui/item/list',
    'util/template/tpl',
    'text!./comment.css',
    'text!./comment.html'
],function(_k,_e,_v,_i,_t,_css,_html,_p,_o,_f,_r){
    var _pro;

    // 列表项构造
    _p._$$CommentItem = _k._$klass();
    _pro = _p._$$CommentItem._$extend(_i._$$ListItem);

    // 外观
    _pro.__initXGui = (function(){
        var _seed_css = _e._$pushCSSText(_css),
            _seed_html = _e._$addNodeTemplate(_html);
        return function(){
            this.__seed_css = _seed_css;
            this.__seed_html = _seed_html;
        };
    })();

    // 结构
    _pro.__initNode = function(){
        this.__super();
        // 0 - 头像图片节点
        // 1 - 用户名节点
        // 2 - 内容节点
        // 3 - 操作行为节点
        var _list = _e._$getByClassName(
            this.__body,'j-flag'
        );
        this.__nface = _list[0];
        this.__nuser = _list[1];
        this.__ncont = _list[2];
        // 事件
        _v._$addEvent(
            _list[3],'click',
            this.__onAction._$bind(this)
        );
    };

    // 刷新
    _pro.__doRefresh = function(_data){
        this.__nface.src = _data.face;
        this.__nuser.innerHTML = _data.username;
        this.__ncont.innerHTML = _data.content;
        // 子评论列表
        if (!!_data.replies){
            // 子评论构造同当前评论项
            this.__items = _t._$getItemTemplate(
                _data.replies,this.constructor,{
                    parent:this.__body,
                    onreply:this.__onReply._$bind(this),
                    ondelete:this.__onDelete._$bind(this)
                }
            );
        }
    };

    // 操作
    _pro.__onAction = function(_event){
        var _node = _v._$getElement(_event,'d:action');
        if (!_node) return;
        // 操作
        switch(_e._$dataset(_node,'action')){
            case 'reply':
                // 分配回复编辑器控件
                // 触发onreply事件
                // TODO
            break;
            case 'delete':
                // 删除确认
                // 触发ondelete事件
                // TODO
            break;
        }
    };

    // TODO

    return _p;
});
```

在上层应用中使用_$getItemTemplate来分配ITEM控件列表

```javascript
NEJ.define([
    'util/template/tpl',
    '/path/to/comment.js'
],function(_t,_i){

    // TODO

    var _list = _t._$getItemTemplate(
        _data.replies,this.constructor,{
            parent:'list-box',
            onreply:function(_data){
                // TODO
            },
            ondelete:function(_data){
                // TODO
            }
        }
    );

});
```


================================================
FILE: doc/WIDGET.md
================================================
# 控件系统

## 概述

控件系统主要用来解决系统复杂性的问题,使得系统不会因为变得复杂而不可控,同时保证其维护性和扩展性

NEJ框架提供了基于常规面向对象的思想构建的控件系统,主要用于:

* 提供通用解决方案的封装支持
* 提供核心功能、分析设计的重用
* 提供跨平台控件及API的支持


## 类模型

因为JavaScript本身没有提供类的概念,在控件系统中提供了一套类模型的解决方案,用以模拟常规面向对象语言中的“类”的概念。

类模型的实现见NEJ框架的base/klass模块

### 类定义

控件提供使用统一的类定义接口 \_$klass 来定义一个类,通过此接口定义的类才具备以下的继承、初始化等特性

```javascript
NEJ.define([
    'base/klass'
],function(_k,_p){
    // 定义一个类
    _p._$$Klass = _k._$klass();

    // TODO
    
    return _p;
});
```

### 类继承

使用 \_$klass 定义的类可以使用 \_$extend 接口来继承其他父类

```javascript
NEJ.define([
    'base/klass'
],function(_k,_p){
    // 定义一个类
    _p._$$Klass = _k._$klass();
    // 继承其他类
    var pro = _p._$$Klass._$extend(Super);

    // TODO
    
    return _p;
});
```

### 类构造

使用 \_$klass 定义的类统一使用 \_\_init 接口来初始化类,所有子类的接口均可以使用 this.\_\_super 方式调用父类同名接口

```javascript
NEJ.define([
    'base/klass'
],function(_k,_p){
    // 定义一个类
    _p._$$Klass = _k._$klass();
    // 继承其他类
    var pro = _p._$$Klass._$extend(Super);
    // 初始化
    pro.__init = function(){
        // 调用父类的__init
        this.__super();
        // TODO something
    };
    
    // TODO
    
    return _p;
});
```

### 类扩展

类的所有方法均定义在类函数的 prototype 对象上

```javascript
NEJ.define([
    'base/klass'
],function(_k,_p){
    // 定义一个类
    _p._$$Klass = _k._$klass();
    // 继承其他类
    var pro = _p._$$Klass._$extend(Super);
    // 初始化
    pro.__init = function(){
        // 调用父类的__init
        this.__super();
        // TODO something
    };

    // private 方法
    pro._privateMethod = function(){

    };
    // protected 方法
    pro.__protectedMethod = function(){
        // TODO
    };
    // public 方法
    pro._$publicMethod = function(){
        // TODO
    };

    // TODO
    
    return _p;
});
```

### 类规范

1. 类命名

   类命名使用前缀\_$$标识,首字母大写,驼峰式,如\_$$Klass,\_$$OneKlass

2. 类方法

   方法分为私有、保护、公共方法三类,各类方法的前缀标识如下

   * 私有方法使用\_(单个下划线)作为前缀,如\_privateMethod
   * 受保护的方法使用\_\_(两个下划线)作为前缀,如\_\_protectedMethod
   * 公共的方法使用\_$(下划线+美元符)作为前缀,如\_$publicMethod

   方法的命名首字母小写,驼峰式,如\_$myApi,\_\_myApi,\_myApi

## 控件模型

控件模型使用类模型来实现,基于类模型的基础做扩展,主要在util/event模块中实现;控件采用分配回收机制,因此控件的生命周期包括以下三个阶段:

1. 控件创建:首次使用时控件创建阶段,主要用于构建控件相关结构、数据等
2. 控件重置:重复使用时控件重置阶段,主要用于处理外部输入数据、事件侦测等
3. 控件回收:回收不用时控件销毁阶段,主要用于销毁重置阶段产生的结构、数据等

### 控件定义

所有的控件均继承自 util/event 模块的 _$$EventTarget 类

```javascript
NEJ.define([
    'base/klass',
    'util/event'
],function(_k,_t,_p){
    var _pro;
    // 定义控件
    _p._$$Widget = _k._$klass();
    // 继承_$$EventTarget
    _pro = _p._$$Widget._$extend(_t._$$EventTarget);

    // TODO

    return _p;
});
```

### 接口重写

控件采用分配回收重用机制,因此控件需实现\_\_init、\_\_reset、\_\_destroy接口

```javascript
NEJ.define([
    'base/klass',
    'util/event'
],function(_k,_t,_p){
    var _pro;
    // 定义控件
    _p._$$Widget = _k._$klass();
    // 继承_$$EventTarget
    _pro = _p._$$Widget._$extend(_t._$$EventTarget);
    // 控件首次创建构造过程
    _pro.__init = function(){
        this.__super();
        // TODO
    };
    // 控件重复使用重置过程
    _pro.__reset = function(_options){
        this.__super(_options);
        // TODO
    };
    // 控件回收销毁过程
    _pro.__destroy = function(){
        this.__super();
        // TODO
    };

    // TODO

    return _p;
});
```

### 扩展实现

其他扩展的业务逻辑根据控件实际需求实现

```javascript
NEJ.define([
    'base/klass',
    'util/event'
],function(_k,_t,_p){
    var _pro;
    // 定义控件
    _p._$$Widget = _k._$klass();
    // 继承_$$EventTarget
    _pro = _p._$$Widget._$extend(_t._$$EventTarget);
    // 控件首次创建构造过程
    _pro.__init = function(){
        this.__super();
        // TODO
    };
    // 控件重复使用重置过程
    // 重置过程可以接受到分配控件时输入的配置信息
    _pro.__reset = function(_options){
        this.__super(_options);
        // TODO
    };
    // 控件回收销毁过程
    _pro.__destroy = function(){
        this.__super();
        // TODO
    };

    // 扩展私有接口
    _pro._myPrivateMethod = function(){
        // TODO
    };
    // 扩展保护接口
    _pro.__myProtectedMethod = function(){
        // TODO
    };
    // 扩展对外接口
    _pro._$myPublicMethod = function(){
        // TODO
    };

    // TODO

    return _p;
});
```

### 事件支持

控件支持自定义事件的触发,在控件的业务逻辑中可根据实际需求通过 \_$dispatchEvent 接口触发自定义事件来与外界进行交互

```javascript
NEJ.define([
    'base/klass',
    'util/event'
],function(_k,_t,_p){
    var _pro;
    // 定义控件
    _p._$$Widget = _k._$klass();
    // 继承_$$EventTarget
    _pro = _p._$$Widget._$extend(_t._$$EventTarget);
    // 控件首次创建构造过程
    _pro.__init = function(){
        this.__super();
        // TODO
    };
    // 控件重复使用重置过程
    // 重置过程可以接受到分配控件时输入的配置信息
    _pro.__reset = function(_options){
        this.__super(_options);
        // TODO
    };
    // 控件回收销毁过程
    _pro.__destroy = function(){
        this.__super();
        // TODO
    };

    // 扩展私有接口
    _pro._myPrivateMethod = function(){
        // TODO
        // 触发自定义的onchange事件
        this._$dispatchEvent(
            'onchange',{
                x:'xxxxx',
                y:'yyyyyyy'
            }
        );
    };
    // 扩展保护接口
    _pro.__myProtectedMethod = function(){
        // TODO
        // 触发自定义的onupdate事件
        this._$dispatchEvent(
            'onupdate',{
                a:'aaaa',
                b:'bbbbbbb'
            }
        );
    };
    // 扩展对外接口
    _pro._$myPublicMethod = function(){
        // TODO
    };

    // TODO

    return _p;
});
```

### 平台适配

控件的平台适配规则遵循《[平台适配系统](./PLATFORM.md)》的规范,可以按照以下步骤实现:

1. 在控件实现文件处构建平台适配目录platform,可以通过nej工具集中nej-widget指令来自动生成控件目录结构,或者使用nej-patch指令来自动生成platform目录结构,如

    ```
      widget
        | - widget.js
        | - platform
                | - widget.js
                | - widget.patch.js
    ```

2. 提取控件涉及的存在平台差异的API,在platform/widget.js中根据W3C/ES规范实现API

    ```javascript
    NEJ.define([
        'base/platform'
    ],function(_m,_p){
        // 存在平台差异的API
        _p.__api1 = function(){
            // TODO
        };
        // 存在平台差异的API
        _p.__api2 = function(){
            // TODO
        }
        // 返回平台差异API集合
        return _p;
    });
    ```

3. 根据平台差异,在platform/widget.patch.js文件中实现各平台的差异化逻辑

    ```javascript
    NEJ.define([
        './widget.js'   // 这里注入标准API集合
    ],function(_h){
        // 根据平台特点重写API实现
        NEJ.patch('TR<=2.0',function(){
            // for ie6-
            _h.__api1 = function(){
                // TODO
            };
        });

        // 根据平台特点采用AOP方式切入平台逻辑
        NEJ.patch('WR',function(){
            // for webkit
            _h.__api2 = _h.__api2._$aop(
                function(_event){
                    // 标准逻辑之前处理业务逻辑
                    // _event.args
                    // _event.value
                    // _event.stopped
                    // TODO
                },
                function(_event){
                    // 标准逻辑之后处理业务逻辑
                    // _event.args
                    // _event.value
                    // _event.stopped
                    // TODO
                }
            );
        });

        // 这里必须返回注入的标准API集合
        return _h;
    });
    ```

4. 控件中使用{platform}注入平台适配API使用

    ```javascript
    NEJ.define([
        'base/klass',
        'util/event',
        '{platform}widget.js'
    ],function(_k,_t,_h,_p){
        var _pro;
        // 定义控件
        _p._$$Widget = _k._$klass();
        // 继承_$$EventTarget
        _pro = _p._$$Widget._$extend(_t._$$EventTarget);
        // 控件首次创建构造过程
        _pro.__init = function(){
            this.__super();
            // TODO
        };
        // 控件重复使用重置过程
        // 重置过程可以接受到分配控件时输入的配置信息
        _pro.__reset = function(_options){
            this.__super(_options);
            // TODO
        };
        // 控件回收销毁过程
        _pro.__destroy = function(){
            this.__super();
            // TODO
        };

        // 扩展私有接口
        _pro._myPrivateMethod = function(){
            // 使用平台适配接口
            _h.__api1();

            // TODO
            // 触发自定义的onchange事件
            this._$dispatchEvent(
                'onchange',{
                    x:'xxxxx',
                    y:'yyyyyyy'
                }
            );
        };
        // 扩展保护接口
        _pro.__myProtectedMethod = function(){
            // TODO
            // 触发自定义的onupdate事件
            this._$dispatchEvent(
                'onupdate',{
                    a:'aaaa',
                    b:'bbbbbbb'
                }
            );
        };
        // 扩展对外接口
        _pro._$myPublicMethod = function(){
            // 使用平台适配接口
            _h.__api2();

            // TODO
        };

        // TODO

        return _p;
    });
    ```

### 控件使用

控件使用分配回收机制而非 new 的方式使用

```javascript
NEJ.define([
    '/path/to/widget.js'
],function(_t){
    // 分配控件
    var _widget = _t._$$Widget._$allocate({
        a:'aaaaaaaa',
        b:'bbbbbbbbbbb',
        c:'ccccccccccccc',
        onchange:function(_event){
            // 控件支持的事件
            // _event.x
            // _event.y

            // TODO
        },
        onupdate:function(_event){
            // 控件支持的事件
            // _event.a
            // _event.b

            // TODO
        }
    });

    // 外界可以调用控件的public方法
    _widget._$myPublicMethod();

    // 回收控件
    // 注意这里必须将原控件持有的引用置空
    _widget = _widget._$recycle();
    // 或者
    _widget._$recycle();
    _widget = null;
});
```

## 控件分类

控件根据其封装元素的差异可以分为通用控件和UI控件两类

* 通用控件:此类控件关注功能业务逻辑的实现,不关注视觉效果
* UI控件:此类控件会构建一套默认的视觉效果,具体功能逻辑由与之匹配的通用控件来实现

由于UI控件在实际项目中差异性比较大,因此NEJ框架会主要关注通用控件的支持,项目中可以根据通用控件结合实际项目视觉效果来实现项目相关的UI控件

### 通用控件

通用控件只需遵循[控件模型](#控件模型)实现即可

### UI控件

UI控件基于控件模型扩展而来,其抽象实现在 ui/base 模块中的 \_$$Abstract 类,UI控件的主要元素包括:

* 样式:控件展示效果样式,独立在控件对应的css文件中
* 结构:控件组成结构,独立在控件对应的html文件中
* 逻辑:控件逻辑实现,独立在控件对应的javascript文件中

一个UI控件典型的目录结构为

```
    widget
      | - widget.css
      | - widget.html
      | - widget.js
```

#### 样式

每个UI控件都使用一个唯一的样式标识,以防止与其他控件样式冲突,样式文件范例如下:

```css
.#<uispace>-parent{position:relative;}
.#<uispace>{position:absolute;border:1px solid #aaa;background:#fff;text-align:left;visibility:hidden;}
.#<uispace> .zitm{height:20px;line-height:20px;cursor:default;}
.#<uispace> .js-selected{background:#1257F9;}
```

这里可以使用 #&lt;KEY&gt; 格式的简单模板来做数据占位,其中

* \#&lt;uispace&gt; - 表示自动生成的样式标识名称
* \#&lt;uispace&gt;-parent - 表示控件节点的父容器节点的样式
* 其他参数可以使用#&lt;KEY&gt;来占位,后续使用时输入{KEY:'XXXXX'}的数据即可

#### 结构

每个UI控件可以关联若干的结构模板,模板规则遵循NEJ的[模板系统](./TEMPLATE.md)规范

单个模板文件范例

```html
<div>
  <div class="zbar">
    <div class="zttl">标题</div>
  </div>
  <div class="zcnt"></div>
  <span class="zcls" title="关闭窗体">×</span>
</div>
```

多个模板文件范例,模板的ID支持使用 #&lt;KEY&gt; 形式的简单模板做ID占位

```html
<textarea name='jst' id='#<icmd>'>
{list xlist as x}
  <div class="zitm zbg ${'js-'|seed}" data-command="${x.cmd}" title="${x.txt}">
    <div class="zicn zbg ${x.icn}">&nbsp;</div>
    <div class="ztxt">${x.txt}</div>
  </div>
{/list}
{if defined("hr")&&!!hr}
  <div class="zbg zisp">&nbsp;</div>
{/if}
</textarea>

<textarea name='jst' id='#<ifnt>'>
  <div class="zsel ${icn} ${'js-'|seed}" data-command="${cmd}">
    <span class="${'js-t-'|seed}">${txt}</span>
    <span class="zarw zbg">&nbsp;</span>
  </div>
</textarea>

<textarea name='jst' id='#<iedt>'>
  <div>
    <div class="ztbar">${toolbar}</div>
    <div class="zarea"></div>
  </div>
</textarea>
```

#### 逻辑

逻辑部分主要用来实现UI控件的核心逻辑,主要分以下几部分功能

* 注入样式处理
* 注入结构处理
* 控件初始化

##### 注入样式

根据[依赖系统](./DEPENDENCY.md)规则,UI控件使用 text! 注入样式,注入的样式通过 base/element 模块中的 \_$pushCSSText 接口做预处理,并返回自动生成的控件样式标识

```javascript
NEJ.define([
    'base/element',
    'ui/base',
    'text!./widget.css'
],function(_e,_i,_css,_p){
    // 将注入的样式做预处理后缓存
    var _seed_css = _e._$pushCSSText(_css);

    // TODO
});
```

如果样式中已做了样式标识无需自动生成则只需缓存样式即可,如

```css
.ui-suggest-parent{position:relative;}
.ui-suggest{position:absolute;border:1px solid #aaa;background:#fff;text-align:left;visibility:hidden;}
.ui-suggest .zitm{height:20px;line-height:20px;cursor:default;}
.ui-suggest .js-selected{background:#1257F9;}
```

```javascript
NEJ.define([
    'base/element',
    'ui/base',
    'text!./widget.css'
],function(_e,_i,_css,_p){
    // 将注入的样式缓存
    _e._$pushCSSText(_css);

    // TODO
});
```

##### 注入结构

根据[依赖系统](./DEPENDENCY.md)规则,UI控件使用 text! 注入结构,注入的结构符合[模板系统](./TEMPLATE.md)规则,后续使用 util/template/tpl 模块中的模板处理接口做处理

单个模板结构注入

```javascript
NEJ.define([
    'base/element',
    'util/template/tpl',
    'ui/base',
    'text!./widget.css',
    'text!./widget.html'
],function(_e,_t,_i,_css,_html,_p){
    // 将注入的样式做预处理后缓存
    var _seed_css = _e._$pushCSSText(_css),
        _seed_html = _t._$addNodeTemplate(_html);

    // TODO
});
```

多个模板结构注入

```javascript
NEJ.define([
    'base/element',
    'util/template/tpl',
    'ui/base',
    'text!./widget.css',
    'text!./widget.html'
],function(_e,_t,_i,_css,_html,_p){
    // 将注入的样式做预处理后缓存
    var _seed_css = _e._$pushCSSText(_css);

    // 这里可以自动生成模板ID
    // 返回 {icmd:'tpl-127363653',ifnt:'tpl-5985857444',iedt:'tpl-48763635374'}
    var _seed = _t._$parseUITemplate(_html);

    // 这里也可以自己指定模板ID
    // 可以指定全部的ID,也可以指定某几个,未指定的ID自动生成
    var _seed = _t._$parseUITemplate(_html,{
        icmd:'abc',
        ifnt:'def',
        iedt:'ghi'
    });

    // TODO
});
```

##### 逻辑实现

UI控件的逻辑实现主要扩展自 ui/base 模块中的 \_$$Abstract 类,需要实现外观的设置和结构的初始化

1. 初始化外观

    ```javascript
    NEJ.define([
        'base/klass',
        'base/element',
        'util/template/tpl',
        'ui/base',
        'text!./widget.css',
        'text!./widget.html'
    ],function(_k,_e,_t,_i,_css,_html,_p){
        var _pro;

        // 定义UI控件
        _p._$$UIWidget = _k._$klass();
        _pro = _p._$$UIWidget._$extend(_i._$$Abstract);

        // 按需完成通用控件接口重写
        // _pro.__init ...
        // _pro.__reset ...
        // _pro.__destroy ...

        // 初始化外观
        // 此过程只会在控件第一次创建时进入
        _pro.__initXGui = (function(){
            // 将注入的样式/结构做预处理后缓存
            var _seed_css = _e._$pushCSSText(_css),
                _seed_html = _t._$addNodeTemplate(_html);
            return function(){
                this.__seed_css = _seed_css;
                this.__seed_html = _seed_html;
            };
        })();

        // TODO

        return _p;
    });
    ```

2. 初始化结构

    ```javascript
    NEJ.define([
        'base/klass',
        'base/element',
        'util/template/tpl',
        'ui/base',
        'text!./widget.css',
        'text!./widget.html'
    ],function(_k,_e,_t,_i,_css,_html,_p){
        var _pro;

        // 定义UI控件
        _p._$$UIWidget = _k._$klass();
        _pro = _p._$$UIWidget._$extend(_i._$$Abstract);

        // 按需完成通用控件接口重写
        // _pro.__init ...
        // _pro.__reset ...
        // _pro.__destroy ...

        // 初始化外观
        // 此过程只会在控件第一次创建时进入
        _pro.__initXGui = (function(){
            // 将注入的样式/结构做预处理后缓存
            var _seed_css = _e._$pushCSSText(_css),
                _seed_html = _t._$addNodeTemplate(_html);
            return function(){
                this.__seed_css = _seed_css;
                this.__seed_html = _seed_html;
            };
        })();

        // 初始化结构
        // 此过程只会在控件第一次创建时进入
        _pro.__initNode = function(){
            // 调用父类接口通过提供的__seed_html构建控件结构
            // 构建好的控件结构可以通过this.__body访问
            this.__super();

            // TODO
        };

        // TODO

        return _p;
    });
    ```

3. 功能实现

    ```javascript
    NEJ.define([
        'base/klass',
        'base/element',
        'util/template/tpl',
        'ui/base',
        'text!./widget.css',
        'text!./widget.html'
    ],function(_k,_e,_t,_i,_css,_html,_p){
        var _pro;

        // 定义UI控件
        _p._$$UIWidget = _k._$klass();
        _pro = _p._$$UIWidget._$extend(_i._$$Abstract);

        // 按需完成通用控件接口重写
        // _pro.__init ...
        // _pro.__reset ...
        // _pro.__destroy ...

        // 初始化外观
        // 此过程只会在控件第一次创建时进入
        _pro.__initXGui = (function(){
            // 将注入的样式/结构做预处理后缓存
            var _seed_css = _e._$pushCSSText(_css),
                _seed_html = _t._$addNodeTemplate(_html);
            return function(){
                this.__seed_css = _seed_css;
                this.__seed_html = _seed_html;
            };
        })();

        // 初始化结构
        // 此过程只会在控件第一次创建时进入
        _pro.__initNode = function(){
            // 调用父类接口通过提供的__seed_html构建控件结构
            // 构建好的控件结构可以通过this.__body访问
            this.__super();

            // TODO
        };

        // 实现控件核心功能
        _pro._myPrivateMethod = function(){
            // TODO
        };
        _pro.__myProtectedMethod = function(){
            // TODO
        };
        _pro._$myPublicMethod = function(){
            // TODO
        };

        // TODO

        return _p;
    });
    ```

##### 控件使用

控件的使用同通用控件,这里需要注意的是UI控件需要输入parent配置参数才能在页面上渲染出来,否则构建的控件只存在于内存中,页面上无法看到

```javascript
NEJ.define([
    '/path/to/ui/widget.js'
],function(_i){
    // 分配控件
    var _uiwidget = _i._$$UIWidget._$allocate({
        parent:document.body,  // 注意这里输入parent
        clazz:'m-ui-widget'
    });

    // 回收控件
    _uiwidget = _uiwidget._$recycle();
});
```

## 控件规范

项目过程中如果觉得有些控件可以通用,分享给其他项目使用,可以将控件提交到NEJ控件仓库,提交的控件遵循以下规范

### 目录规范

提交的通用控件目录结构如下所示(注:目录及文件命名中不得出现"."等特殊字符)

```
  widget
    | - test
    | - demo
    | - platform
    | - widget.js
```

提交的UI控件的目录结构为

```
  widget
    | - test
    | - demo
    | - platform
    | - widget.js
    | - widget.css
    | - widget.html
```

#### test

用于自动化测试控件的代码,后期会统一规范控件的测试方式 (TODO:测试规范)

#### demo

用于放置当前控件的使用场景及使用范例

#### platform

根据NEJ[平台适配系统](./PLATFORM.md)规则,如果控件需要做平台适配则在此目录下实现适配接口,如果无需平台适配则可以不提交此目录

#### widget.css

控件关联的样式文件,如无关联样式可不提交此文件

#### widget.html

控件关联的结构文件,遵循NEJ[模板系统](./TEMPLATE.md)规范,如无关联结构可不提交此文件

#### widget.js

控件核心业务逻辑实现文件


### 注释规范

所有注释遵循[JSDOC3](http://usejsdoc.org/)规范,注释描述支持markerdown语法

#### 文件注释

文件起始位置注释文件的描述信息、作者、版本等

```javascript
    /*
     * ------------------------------------------
     * 控件描述内容
     *
     * @version  1.0
     * @author   genify(caijf@corp.netease.com)
     * ------------------------------------------
     */
```

#### 模块注释

使用@module标记注释当前文件的模块,模块名称可被[依赖系统](./DEPENDENCY.md)直接引入使用

```javascript
    /** @module util/event */
```

#### 类注释

使用@class、@extends标记注释类及继承关系

```javascript
    /**
     * 标签切换控件封装
     *
     * 结构举例
     *
     * ```html
     *   <div id="box">
     *       <a>1</a>
     *       <a>2</a>
     *       <a class="js-disabled">3</a>
     *       <a>4</a>
     *   </div>
     * ```
     *
     * 脚本举例
     *
     * ```javascript
     * NEJ.define([
     *     'util/tab/tab'
     * ],function(_t){
     *     // 实例化控件
     *     var _tab = _t._$$Tab._$allocate({
     *         list:_e._$getChildren('box'),
     *         index:1,
     *         onchange:function(_event){
     *             // TODO
     *         }
     *     });
     *     // 使用控件
     *     _tab._$go(2);
     * });
     * ```
     *
     * @class   module:util/tab/tab._$$Tab
     * @extends module:util/event._$$EventTarget
     *
     * @param    {Object}  config   - 可选配置参数
     * @property {Array}   list     - 标签项列表
     * @property {Number}  index    - 初始选中项索引值,默认为0
     * @property {String}  event    - 触发选择事件名称,默认为click
     * @property {Boolean} inverse  - 是否反过程,true表示选中时删除选中样式,否则选中时添加样式
     * @property {String}  disabled - 选项禁用样式,默认为js-disabled
     * @property {String}  selected - 选中样式名,默认为js-selected
     */
```

#### 事件注释

使用@event标记注释控件支持的事件

```javascript
    /**
     * 标签切换事件,输入{last:1,index:5}
     *
     * ```javascript
     * NEJ.define([
     *     'util/tab/tab'
     * ],function(_t){
     *     // 实例化控件
     *     var _tab = _t._$$Tab._$allocate({
     *         list:_e._$getChildren(_e._$get('box')),
     *         index:1,
     *         onchange:function(_event){
     *             // _event.last   上一次的tab索引
     *             // _event.index  需要切换到的tab索引
     *             // _event.list   节点列表
     *             // _event.data   节点上通过data-value设置的内容
     *             // TODO
     *         }
     *     });
     * });
     * ```
     *
     * @event    module:util/tab/tab._$$Tab#onchange
     * @param    {Object}  event   - tab信息
     * @property {Number}  last    - 上一次的tab索引
     * @property {Number}  index   - 需要切换到的tab索引
     * @property {Array}   list    - 节点列表
     * @property {String}  data    - 节点上通过data-value设置的内容
     * @property {Boolean} stopped - 是否阻止触发节点的默认事件,回调过程中如果设置为false则后续继续触发节点的默认事件
     */
```

#### 方法注释

使用@method标记注释控件接口,使用@private、@protected标记注释私有和受保护的方法

```javascript
    /**
     * 设置标签选中状态
     *
     * @protected
     * @method module:util/tab/tab._$$Tab#__doTabItemSelect
     * @param  {Node}    arg0 - 标签节点
     * @param  {Boolean} arg1 - 是否选中
     * @return {Void}
     */
```

```javascript
    /**
     * 切换到指定索引位置
     *
     * ```javascript
     *   // 切换到索引为2的位置,如果当前索引为2则不触发回调
     *   _tab._$go(2);
     *   // 切换索引为2,如果当前索引为2也触发onchange回调
     *   _tab._$go(2,true);
     * ```
     *
     * @method module:util/tab/tab._$$Tab#_$go
     * @param  {Number}  arg0 - 索引值
     * @param  {Boolean} arg1 - 是否强行触发onchange事件
     * @return {Void}
     */
```

### 编码规范

#### 前缀规范

控件编码使用前缀标识变量使用范围

| 前缀 | 说明 |
| :--  | :--  |
| \_   | 私有属性、方法,局部变量,仅限于当前控件范围内使用 |
| \_\_ | 受保护的属性、方法,控件范围及所有子类可使用 |
| \_$  | 对外属性、方法,控件外可直接调用 |
| \_$$ | 类名前缀,控件外可直接使用 |
| on   | 事件名前缀,控件外可直接使用 |

#### 命名规范

控件命名遵循以下规则便于识别

* 类名首字母大写,驼峰形式,如\_$$MyClassName等
* 属性、方法名首字母小写,驼峰形式,如 \_myMethod、\_\_myProtectedMethod、\_$doSomething等
* 事件名称全小写,采用名称+动词形式,如 onchange、onlistload等




================================================
FILE: doc/guide/Build.Scalable.Web.System/Module.md
================================================
# 构建高可伸缩性的WEB交互式系统(2) - 模块的可伸缩性

## 概述

可伸缩性是一种对软件系统处理能力的设计指标,高可伸缩性代表一种弹性,在系统扩展过程中,能够保证旺盛的生命力,通过很少的改动,就能实现整个系统处理能力的增长。

在系统设计的时候,充分地考虑系统的可伸缩性,一方面能够极大地减少日后的维护开销,并帮助决策者对于投资所能获得的回报进行更加精准的估计;另一方面,高可伸缩性的系统往往会具有更好的容灾能力,从而提供更好的用户体验。

WEB交互式系统的可伸缩性主要体现在两个方面:

* 平台的可伸缩性:随着WEB技术的发展,越来越多的平台开始使用WEB技术来构建系统,一方面不同的平台提供的环境支持存在着各种差异;另一方面随着平台的发展,不断的会有一些旧平台退出历史舞台,新平台转而成为主流平台;因此构建的WEB系统需要能够快速的响应此类变化就需要其具备良好的平台伸缩性
* 模块的可伸缩性:随着系统功能不断增删更新需求的变化,系统可能会变得越来越复杂,冗余信息也可能会越来越多,改动所带来的影响范围也可能会越来越大,因此良好的模块伸缩性可保证系统具有良好的可维护性,让系统始终处于最佳状态

WEB交互式系统的主要应用包括:

* 桌面端/移动端网站类系统(如 [网易云课堂](http://study.163.com/)、[Lofter移动WEB版](http://www.lofter.com/)等)
* 移动混合应用(如 [网易云相册IPad版](http://photo.163.com/cloudphotos/)、[Lofter](http://www.lofter.com/app)等)
* 桌面混合应用(如 [网易云音乐PC版](http://music.163.com/#/download)、[网易邮箱助手](http://mailease.163.com/)等)

## 模块的可伸缩性

WEB交互式系统对模块的可伸缩性同样表现为:

* 可扩展性:对于系统新增的功能需求能够快速响应支持
* 可缩减性:对于系统退化的模块能够以最小的修改方式剔除

这里我们提供一套模块调度的系统架构模式用于支持单页富应用系统的设计架构、模块拆分、模块重组、调度管理等功能

### 模块

这里我们定义的模块是指从系统中拆分出来的可与用户进行交互完成一部分完整功能的独立单元

#### 模块组成

因为这里描述的模块可独立与用户完成交互功能,因此模块会包含以下元素

* 样式:定义模块的效果
* 结构:定义模块的结构
* 逻辑:实现模块的功能

以上元素对于一个WEB系统开发者来说并不陌生,而我们只需要寻求一种形式将这些内容封装起来即可

#### 模块封装

从模块的组成我们可以看到系统中分离出来的模块可能会长成这个样子,比如module.html就是我们分离出来的一个模块

当然这里也可以用脚本文件封装,样式和结构采用注入形式,我们这里以html文件封装举例

```html
<!-- 模块样式 -->
<style>
    .m-mdl-1 .a{color:#aaa;}
    .m-mdl-1 .b{color:#bbb;}

    /* 此处省略若干内容 */
</style>

<!-- 模块结构 -->
<div class="m-mdl-1">
  <p class="a">aaaaaaaaaaaaaaaaaaa</p>
  <p class="b">bbbbbbbbbbbbbbbbbbb</p>

  <!-- 此处省略若干内容 -->
</div>

<!-- 模块逻辑 -->
<script>
    (function(){
        var a = 'aaa';
        var b = 'bbb';

        // 此处省略若干内容
    })();
</script>
```

而这个模块在用户需要时加载到客户端,并展现出来跟用户进行交互,完成功能,但是我们会发现如果系统预加载了此模块或者模块在parse时这些内容会被直接执行,而这个结果并不是我们需要的,因此我们需要将模块的各元素文本化处理,文本化处理有多种方式,如作为文本script、textarea等标签内容,因此module.html里的模块我们可以封装成如下样子,以textarea举例:

```html
<!-- 模块样式 -->
<textarea name="css">
    .m-mdl-1 .a{color:#aaa;}
    .m-mdl-1 .b{color:#bbb;}

    /* 此处省略若干内容 */
</textarea>

<!-- 模块结构 -->
<textarea name="html">
    <div class="m-mdl-1">
      <p class="a">aaaaaaaaaaaaaaaaaaa</p>
      <p class="b">bbbbbbbbbbbbbbbbbbb</p>

      <!-- 此处省略若干内容 -->
    </div>
</textarea>

<!-- 模块逻辑 -->
<textarea name="js">
    (function(){
        var a = 'aaa';
        var b = 'bbb';

        // 此处省略若干内容
    })();
</textarea>
```

### 管理依赖

从系统中拆分出来的模块之间是存在有一定关系的,如一个模块的呈现必须依赖另外一个模块的呈现,下面我们会以一个简单的例子来讲解模块之间的依赖管理,如下图是我们的一个单页应用系统

![单页富应用范例](http://nej.netease.com/images/sample.gif)

从上图不难看出整个系统包含以下几部分内容:

* 日志管理

  * 日志:日志列表,可切换收件箱/草稿箱/回收站/标签
  * 标签:标签列表,可转至日志按标签查看列表

* 博客设置

  * 账号管理

    * 基本资料:用户基本资料设置表单
    * 个人经历:个人经历填写表单

  * 权限设置:权限设置表单

而这些模块之间的层级关系则如下所示

![模块层级结构图](http://img0.ph.126.net/7ixXaTU-uFtbe0pKCWCgPA==/6597270977286264717.png)

针对交互式系统的这种层级架构典型的模式可以参阅[PAC(Presentation-Abstraction-Control)模式](http://en.wikipedia.org/wiki/Presentation%E2%80%93abstraction%E2%80%93control) 或者 [HMVC(Hierarchical model–view–controller)模式](http://en.wikipedia.org/wiki/Hierarchical_model%E2%80%93view%E2%80%93controller)

然而在WEB交互式系统的实践过程中我们发现这种模式会存在一些缺陷:

* 由于每个父模块自己维护了所有的子模块,因此父子模块之间耦合性过强,父模块必须耦合所有子模块
* 由于模块之间不能直接越级调用,因此子模块需要其他模块协助时必须层层向上传递事件,如果层级过深则会影响到系统效率
* 模块的增删等变化导致的变更涉及的影响较大,删除中间节点上的模块可能导致相邻的若干模块的变更
* 多人协作开发系统时存在依赖关系的模块会导致开发人员之间的紧密耦合

在这里我们给出了一种基于模块标识的依赖管理配置方案,可以彻底的将模块进行解耦,每个模块可以独立完整的完成自己的交互功能,而系统的整合则可以通过配置的方式灵活的重组各模块,模块的增删操作只需修改配置即可完成,而无需影响到具体业务逻辑

下文我们会通过以上例子来讲解此方案的原理和实际操作方式

#### 模块标识

因为本方案会基于模块标识做配置,因此在介绍方案之前我们先介绍一下模块标识,这里我们给模块标识取名为**UMI**(Uniform Module Identifier)统一模块标识,下文简称UMI,遵循以下规则约定

* 格式同URI的Path部分,如 /m/m0/
* 必须以“/”符开始
* 私有模块必须以“/?”开始
* 承载模块的依赖关系,如 /m/m0/ 和 /m/m1/ 表明这两个标识对应模块的父模块标识均为 /m

每个UMI均可唯一标识一个模块及模块在系统中的依赖关系,在模块章节我们介绍了一个模块可以用一个html进行封装,因此我们可以得到以下结果

![UMI与模块地址映射关系图](http://img0.ph.126.net/DCSPjxzVgjlgc8B983YSSg==/2673167853921558567.png)

每个UMI均可映射到一个模块实现文件,这样我们就可以将模块从具体实现中解耦出来,对模块的增删修改操作只需调整UMI和模块文件的映射关系即可,而无需涉及具体业务逻辑的修改

#### 模块依赖

在解决了模块与实现分离的问题后,我们接下来需要将层级式的模块扁平化来解耦模块之间的依赖关系

回到前面的例子,模块之间的层级关系如下图所示

![模块层级关系图](http://img1.ph.126.net/NFbfUjokCb1KsI8tCj04xw==/6608203421400487879.jpg)

如果我们将图中的依赖关系进行抽象分离后可以发现所有的模块即可呈现扁平的状态

![模块关系分离图](http://img1.ph.126.net/XByNuSHTyJ_OzmAFIcqGKQ==/6608480498329578217.png)

而对于模块之前的依赖关系的管理在所有系统中都是一致的,但是每个模块的具体功能实现是由系统来决定的,不同的系统是截然不同的,因此本方案提供的解决方案主要是用来维护模块之间的依赖关系的

从上图我们可以比较清楚的看到模块之间的依赖关系呈现树状结构,因此我们会以树的结构来组织维护模块之间的依赖关系,我们称之为依赖关系树,而当我们将这棵树上的任意节点与根节点之间的路径用“/”分隔序列化后发现刚好与我们提供的UMI是匹配的,因此组成系统的模块的UMI可以跟依赖关系树的节点一一对应起来,如下图所示

![依赖关系树节点序列化成UMI](http://img2.ph.126.net/cu36voh3bRXFx530aW6nkg==/6608921402492317376.png)

在模块标识章节我们介绍了UMI与模块封装文件可以相互映射,因此依赖关系树上的节点可以直接与模块的实现文件做一一对应,如下图所示

![依赖关系树映射模块实现文件](http://img1.ph.126.net/6Pfkz1_4dd32jnL60QQ1Kw==/6608598146073751639.png)

至此,我们将垂直层级依赖的模块通过依赖关系树分解成了无任何关系的扁平模块结构

#### 模块组合

模块只需要有个呈现容器即可渲染出来,因此模块如果需要能够做任意组合,只需将模块分成两种类型:提供容器的模块和使用容器的模块即可,当然一个模块可同时兼具提供容器和使用容器的功能,提供容器的模块和使用容器的模块可任意组合

![模块组合](http://img2.ph.126.net/D446SK2cBk_aGZFLVRwIew==/3669307796500606643.png)

对于模块组合的配置代码范例

```javascript
'/m/blog/list/':{
    module:'module/layout/blog.list/index.html',
    composite:{
        box:'/?/blog/box/',
        tag:'/?/blog/tag/',
        list:'/?/blog/list/',
        clazz:'/?/blog/class/'
    }
}
```

### 调度策略

在将模块扁平化后,各模块就可以安排给不同的开发人员进行功能实现和测试了,各模块完成后根据依赖关系树进行系统整合,系统整合后各模块会遵循一定的调度策略进行调度

#### 模块状态

根据模块调度的阶段划分,模块的状态可以分为以下四种:

* 模块构建:构建模块结构
* 模块显示:将模块渲染到指定的容器中
* 模块刷新:根据外界输入的参数信息获取数据并展示(这里主要做数据处理)
* 模块隐藏:模块放至内存中,回收由显示和刷新阶段产生的额外数据及结构

调度策略主要控制模块在这几个阶段之间的转换规则

#### 模块显示

当用户请求显示一个模块时各模块会遵循以下步骤进行调度,假设请求显示 /m/blog/list/ 模块

![显示调度策略](http://img1.ph.126.net/ip4S9-mTCAH943VGwXZ1OQ==/6619563575537201227.png)

1. 检查目标节点到根节点路径上注册的模块,如果注册的是模块的实现文件地址,则请求载入模块实现文件
2. 如果节点所在的模块的所有祖先节点已显示,则当前模块可被显示出来,否则等待祖先模块的显示调度
3. 模块载入后根据第二步骤原则尝试调度目标模块的显示

#### 模块切换

当用户从一个模块切换到另外一个模块时各模块遵循以下步骤调度,假设从 /m/blog/list/ 切换到 /m/setting/account/edu/ 模块

![模块切换](http://img0.ph.126.net/ca9IdYk2ezLL-zN8vRqI4Q==/6619574570653479153.png)

1. 查找源模块与目标模块的公共父节点

   ![查找公共模块](http://img2.ph.126.net/G36CYcFsck1w8Uf0k7oOIw==/6608672912864439374.png)

2. 从源节点到公共节点之间的模块调度隐藏操作

   ![隐藏模块](http://img0.ph.126.net/kCydlPd-q7yUIQ2rc65Eyg==/6608818048399307052.png)

3. 从根节点到公共节点之间的模块调度刷新操作

   ![刷新模块](http://img2.ph.126.net/sN7NPW5etJ0FVjv-TyBL9w==/6608677310910950450.png)

4. 从公共节点到目标节点之间的模块调度显示操作

   ![显示模块](http://img1.ph.126.net/DwnLyinZy9ZG8IX_uwOmsg==/6599306173308730570.png)

### 消息通道

大部分时候我们不建议使用模块之前的消息通信,实践中也存在一些特殊情况会需要模块之前的消息通信,这里提供两种方式的消息通讯

* 点对点的消息:一个模块发送消息时明确指定目标模块的UMI
* 观察订阅消息:一个模块可以对外申明发布了什么样的消息,有需要的模块可以订阅该模块UMI上的消息

### 实例分析

[NEJ框架](https://github.com/NetEaseWD/NEJ)根据以上描述对此套架构模式做了实现,我们用上面的具体实例讲解如何使用NEJ中的模块调度系统来拆分一个复杂系统、开发测试模块、整合系统等

#### 系统分解

##### 绘制层级关系图

当我们拿到一个复杂系统时根据交互稿可以绘制出组成系统的模块的层级关系图,并确定系统对外可访问的模块

![模块层级关系图](http://img1.ph.126.net/UmNMLNzb-Vjdtv8skT8kqg==/6608742182096989774.png)

##### 抽象依赖关系树

从模块的层级关系图中我们可以非常方便的抽象出模块的依赖关系树

![抽象依赖关系树](http://img1.ph.126.net/BK5_LhDhh01addpDJuY4Yw==/6619331578583739475.png)

将抽象出来的依赖关系树根据UMI规则进行格式化,格式化的主要操作包括

* 增加一个名称为“/”的根结点(也可将“m”结点改为“/”)
* 每个结点增加“/”的子节点作为默认节点

![格式化依赖关系树](http://img2.ph.126.net/5b3ByaubY0XFOlnU1PZHAw==/6619382156118617138.png)

至此输出的依赖关系树,具有以下特性:

* 任何一个结点(除根结点外)到根结点路径上的结点名称用“/”分隔组合起来即为结点的UMI值,如list结点的UMI值为/m/blog/list
* 任何结点上的模块都依赖于他祖先结点(注册有模块)上的模块存在,如blog结点和list结点均注册有模块,则list结点上的模块显示必须以blog结点上的模块的显示为先决条件

##### 确定对外模块注册节点

五个对外可访问的模块:日志、标签、基本资料、个人经历、权限设置,在依赖关系树中找到合适的结点(叶子结点,层级关系树在依赖关系树中对应的结点或“/”结点)来注册对外可访问的模块

![对外模块注册节点](http://img0.ph.126.net/vC28JzPSddgzZdpEAeBZ2w==/6619485510211628655.png)

##### 确定布局模块注册节点

从可访问模块注册的结点往根结点遍历,凡碰到两模块交叉的结点即为布局模块注册结点,系统所需的组件相关的模块可注册到根结点,这样任何模块使用的时候都可以保证这些组件已经被载入

![布局模块注册节点](http://img1.ph.126.net/Jjp0GCDfh2eXvaxJvhiCeQ==/6619215030351195733.png)

##### 映射模块功能

原则:结点的公共父结点实现结点上注册的模块的公共功能

举例:blog结点和setting结点的公共父结点为m结点,则我们可以通过切换blog模块和setting模块识别不变的功能即为m模块实现的功能,同理其他模块

![功能映射](http://img2.ph.126.net/64xhHIfGy4qdQKSV5Bym-g==/2679360303409193237.png)

##### 分解复杂模块

进一步分解复杂模块,一般需要分解的模块包括:

* 可共用模块,比如日志列表,可以在日志管理页面呈现,也可以在弹层中显示
* 逻辑上无必然联系的模块,如日志模块中日志列表与右侧的按标签查看的标签列表之间没有必然的联系,任何一个模块的移除或添加都不会影响到另外一个模块的业务逻辑

![分解复杂模块](http://img1.ph.126.net/wRM1uAxQDRFhfAzlNw8b5Q==/6599274287471525959.png)

至此我们可以得到两棵系统分解后的依赖关系树

对外模块依赖关系树

![对外模块依赖关系树](http://img2.ph.126.net/6yAKfH-sST3-RGc0xIxU2g==/6608788361585356880.png)

私有模块依赖关系树

![私有模块依赖关系树](http://img1.ph.126.net/KgimU46Zn4ZnUtxiBrXdKw==/6608191326772584502.png)

##### 绘制模块功能规范表

本例中为了说明分解过程将所有可分解的模块都做了分解,实际项目看具体情况,比如这里的/m模块组合的/?/tab/模块的功能可以直接在/m模块中实现,而不需要新建一个/?/tab/模块来实现这个功能

规范表范例如下所示

![功能规范表](http://img1.ph.126.net/e_0ZFwbefuXT5bZ6bq17Rg==/1496320976294650210.png)

#### 构建目录

##### 项目目录

项目目录的构建如下图所示

![项目目录](http://img0.ph.126.net/7mfjeInfGILw-tWQznxxLA==/6608777366469080397.png)

各目录说明

```
webroot                    项目前端开发相关目录
   |- res                  静态资源文件目录,打包时可配置使用到该目录下的静态资源带版本信息
   |- src                  前端源码目录,最终发布时该目录不会部署到线上
       |- html
            |- module      单页面模块目录,系统所有模块的实现均在此目录下
            |- app.html    单页面入口文件
```

##### 模块单元目录

根据模块封装规则一个模块单元由以下几部分组成:

* 模块测试:模块实现的功能可以通过模块测试页面独立进行测试
* 模块结构:模块所涉及的结构分解出来的若干模板集合
* 模块逻辑:根据模块规范实现的模块业务逻辑,从模块基类继承
* 模块样式:模块特有的样式,一般情况下这部分样式可以直接在css目录下实现

结构范例如下所示

![模块单元目录](http://img0.ph.126.net/o6dTV8RpqAtS63zxdz8sEg==/6608917004445806742.png)

至此我们可以得到所有模块的目录结构如下所示

![模块目录结构](http://img0.ph.126.net/2cMlaKsMBzOfAPPdH9IzkA==/6608797157678379135.png)

#### 模块实现

##### 结构

这里我们假设系统的静态页面已经做完,这里的模块实现只是在原有结构的基础上进行结构分解和业务逻辑的实现,结构部分内容主要将模块相关的静态结构拆分成若干NEJ的模板,注意:

* 模板中的外联资源如css,js文件地址如果使用的是相对路径则均相对于模块的html文件路径
* 模板集合中的外联资源必须使用@TEMPLATE标记标识,这个在后面打包发布章节会详细介绍

NEJ模板说明

![NEJ模板](http://img2.ph.126.net/W_RhwYyx4kbPPGMpcVoUwQ==/6608414527633039667.png)

模块结构举例

```html
<meta charset="utf-8"/>

<textarea name="txt" id="m-ifrm-module">
  <div class="n-login">
    <div class="iner j-flag">
      <span class="cls j-flag">×</span>
      <span class="min j-flag">-</span>
    </div>
    <div class="cnt j-cnt"></div>
  </div>
</textarea>


<textarea name="js" data-src="./index.css"></textarea>
<textarea name="js" data-src="./index.js"></textarea>

```

##### 逻辑

依赖util/dispatcher/module模块,从_$$ModuleAbstract扩展一个项目的模块基类,完成项目中模块特有属性、行为的抽象

```javascript
/*
 * ------------------------------------------
 * 项目模块基类实现文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
NEJ.define([
    'base/klass',
    'util/dispatcher/module'
],function(_k,_t,_p){
    // variable declaration
    var _pro;
    /**
     * 项目模块基类对象
     * @class   {_$$Module}
     * @extends {_$$ModuleAbstract}
     * @param   {Object}  可选配置参数,已处理参数列表如下所示
     */
    _p._$$Module = _k._$klass();
    _pro = _p._$$Module._$extend(_t._$$ModuleAbstract);
    /**
     * 操作
     * @param  {Object}
     * @return {Void}
     */
    _pro.__doSomething = function(_args){
        // TODO
    };

    // TODO

    return _p;
});
```

根据模块状态的划分,我们在实现一个模块时需要实现以下几个接口

![模块各阶段接口](http://img0.ph.126.net/8XyKVwG3dzG0dK59qteZFw==/6619467918025585071.png)

各阶段对应的接口:

* 构建 - \_\_doBuild:构建模块结构,缓存模块需要使用的节点,初始化组合控件的配置参数
* 显示 - \_\_onShow:将模块放置到指定的容器中,分配组合控件,添加相关事件,执行\_\_onRefresh的业务逻辑
* 刷新 - \_\_onRefresh:根据外界输入的参数信息获取数据并展示(这里主要做数据处理)
* 隐藏 - \_\_onHide:模块放至内存中,回收在\_\_onShow中分配的组合控件和添加的事件,回收\_\_onRefresh中产生的视图(这里尽量保证执行完成后恢复到\_\_doBuild后的状态)

具体模块实现举例

```javascript
/*
 * ------------------------------------------
 * 项目模块实现文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
NEJ.define([
    'base/klass',
    'util/dispatcher/module',
    '/path/to/project/module.js'
],function(_k,_e,_t,_p){
    // variable declaration
    var _pro;
    /**
     * 项目模块对象
     * @class   {_$$ModuleDemo}
     * @extends {_$$Module}
     * @param   {Object} 可选配置参数
     */
    _p._$$ModuleDemo = _k._$klass();
    _pro = _p._$$ModuleDemo._$extend(_t._$$Module);
    /**
     * 构建模块,主要处理以下业务逻辑
     * - 构建模块结构
     * - 缓存后续需要使用的节点
     * - 初始化需要使用的组件的配置信息
     * @return {Void}
     */
    _pro.__doBuild = function(){
        this.__super();
        // TODO
    };
    /**
     * 显示模块,主要处理以下业务逻辑
     * - 添加事件
     * - 分配组件
     * - 处理输入信息
     * @param  {Object} 输入参数
     * @return {Void}
     */
    _pro.__onShow = function(_options){
        this.__super(_options);
        // TODO
    };
    /**
     * 刷新模块,主要处理以下业务逻辑
     * - 分配组件,分配之前需验证
     * - 处理输入信息
     * - 同步状态
     * - 载入数据
     * @return {Void}
     */
    _pro.__onRefresh = function(_options){
        this.__super(_options);
        // TODO
    };
    /**
     * 隐藏模块,主要处理以下业务逻辑
     * - 回收事件
     * - 回收组件
     * - 尽量保证恢复到构建时的状态
     * @return {Void}
     */
    _pro.__onHide = function(){
        this.__super();
        // TODO
    };
    // notify dispatcher
    _e._$regist(
        'umi_or_alias',
        _p._$$ModuleDemo
    );

    return _p;
});
```

##### 消息

###### 点对点消息

模块可以通过\_\_doSendMessage接口向指定UMI的模块发送消息,也可以通过实现\_\_onMessage接口来接收其他模块发给他的消息

发送消息

```javascript
_pro.__doSomething = function(){

    // TODO

    this.__doSendMessage(
        '/m/setting/account/',{
            a:'aaaaaa',
            b:'bbbbbbbbb'
        }
    );
};
```

接收消息

```javascript
_pro.__onMessage = function(_event){
    // _event.from 消息来源
    // _event.data 消息数据,这里可能是 {a:'aaaaaa',b:'bbbbbbbbb'}

    // TODO
};
```

###### 发布订阅消息

发布消息

```javascript
_pro.__doSomething = function(){

    // TODO

    this.__doPublishMessage(
        'onok',{
            a:'aaaaaa',
            b:'bbbbbbbb'
        }
    );
};
```

订阅消息

```javascript
_pro.__doBuild = function(){

    // TODO

    this.__doSubscribeMessage(
        '/m/message/account/','onok',
        this.__onMessageReceive._$bind(this)
    );
};
```

##### 自测

创建html页面,使用模板引入模块实现文件

```html
<!-- template box -->
<div id="template-box" style="display:none;">
  <textarea name="html" data-src="../index.html"></textarea>
</div>
```

模块放至document.mbody指定的容器中

```javascript
NEJ.define([
    'util/dispatcher/test'
],function(_e){
    document.mbody = 'module-id-0';
    // test module
    _e._$testByTemplate('template-box');
});
```

#### 系统整合

##### 映射依赖关系树

系统整合时,我们只需要将依赖关系树中需要注册模块的节点同模块实现文件进行映射即可

对外模块整合

![对外模块整合](http://img2.ph.126.net/CQe6y5Bdgkl4bA5VsSIwzA==/6608893914701623469.png)

私有模块整合

![私有模块整合](http://img0.ph.126.net/upKrOu1fEmK_Q6x2jc-ibA==/6619430534630239416.png)

##### 提取系统配置信息

规则配置举例

```javascript
  rules:{
      rewrite:{
          '404':'/m/blog/list/',
          '/m/blog/list/':'/m/blog/',
          '/m/setting/account/':'/m/setting/'
      },
      title:{
          '/m/blog/tag/':'日志标签',
          '/m/blog/list/':'日志列表',
          '/m/setting/permission/':'权限管理',
          '/m/setting/account/':'基本资料',
          '/m/setting/account/edu/':'教育经历'
      },
      alias:{
          'system-tab':'/?/tab/',
          'blog-tab':'/?/blog/tab/',
          'blog-list-box':'/?/blog/box/',
          'blog-list-tag':'/?/blog/tag/',
          'blog-list-class':'/?/blog/class/',
          'blog-list':'/?/blog/list/',
          'setting-tab':'/?/setting/tab/',
          'setting-account-tab':'/?/setting/account/tab/',

          'layout-system':'/m',
          'layout-blog':'/m/blog',
          'layout-blog-list':'/m/blog/list/',
          'layout-setting':'/m/setting',
          'layout-setting-account':'/m/setting/account',

          'blog-tag':'/m/blog/tag/',
          'setting-edu':'/m/setting/account/edu/',
          'setting-profile':'/m/setting/account/',
          'setting-permission':'/m/setting/permission/'
      }
  }
```

模块配置举例

```javascript
  modules:{
      '/?/tab/':'module/tab/index.html',
      '/?/blog/tab/':'module/blog/tab/index.html',
      '/?/blog/box/':'module/blog/list.box/index.html',
      '/?/blog/tag/':'module/blog/list.tag/index.html',
      '/?/blog/class/':'module/blog/list.class/index.html',
      '/?/blog/list/':'module/blog/list/index.html',
      '/?/setting/tab/':'module/setting/tab/index.html',
      '/?/setting/account/tab/':'module/setting/account.tab/index.html',

      '/m':{
          module:'module/layout/system/index.html',
          composite:{
              tab:'/?/tab/'
          }
      },
      '/m/blog':{
          module:'module/layout/blog/index.html',
          composite:{
              tab:'/?/blog/tab/'
          }
      },
      '/m/blog/list/':{
          module:'module/layout/blog.list/index.html',
          composite:{
              box:'/?/blog/box/',
              tag:'/?/blog/tag/',
              list:'/?/blog/list/',
              clazz:'/?/blog/class/'
          }
      },
      '/m/blog/tag/':'module/blog/tag/index.html',

      '/m/setting':{
          module:'module/layout/setting/index.html',
          composite:{
              tab:'/?/setting/tab/'
          }
      },
      '/m/setting/account':{
          module:'module/layout/setting.account/index.html',
          composite:{
              tab:'/?/setting/account/tab/'
          }
      },
      '/m/setting/account/':'module/setting/profile/index.html',
      '/m/setting/account/edu/':'module/setting/edu/index.html',
      '/m/setting/permission/':'module/setting/permission/index.html'
  }
```

##### 模块组合

模块通过__export属性开放组合模块的容器,__export中的parent为子模块的容器节点,顶层模块(如 “/m”)可以通过重写__doParseParent来明确指定应用所在容器

```javascript
_pro.__doBuild = function(){
    this.__body = _e._$html2node(
        _e._$getTextTemplate('module-id-l2')
    );
    // 0 - box select
    // 1 - class list box
    // 2 - tag list box
    // 3 - sub module box
    var _list = _e._$getByClassName(this.__body,'j-flag');
    this.__export = {
        box:_list[0],
        clazz:_list[1],
        tag:_list[2],
        list:_list[3],
        parent:_list[3]
    };
};
```

通过composite配置模块组合

```javascript
'/m/blog/list/':{
    module:'module/layout/blog.list/index.html',
    composite:{
        box:'/?/blog/box/',
        tag:'/?/blog/tag/',
        list:'/?/blog/list/',
        clazz:'/?/blog/class/'
    }
}
```

模块组合时可以指定组合模块的处理状态
* onshow  - 这里配置的组合模块仅在模块显示时组合,后续的模块refresh操作不会导致组合模块的refresh,适合于模块在显示后不会随外界输入变化而变化的模块
* onrefresh  -  这里配置的模块在模块显示时组合,后续如果模块refresh时也会跟随做refresh操作,适用于组合的模块需要与外部输入同步的模块
* 不指定onshow或者onrefresh的模块等价于onrefresh配置的模块

```javascript
composite:{
    onshow:{
        // 模块onshow时组合
        // 组合的模块在模块onrefresh时不会刷新
    },
    onrefresh{
        // 模块onshow时组合
        // 组合的模块在模块onrefresh时也同时会刷新
    }
    // 这里配置的组合模块等价于onrefresh中配置的模块
}
```

##### 启动应用

根据配置启动应用

```javascript
NEJ.define([
    'util/dispatcher/dispatcher'
],function(_e){
    _e._$startup({
        // 规则配置
        rules:{
            rewrite:{
                // 重写规则配置
            },
            title:{
                // 标题配置
            },
            alias:{
                // 别名配置
                // 建议模块实现文件中的注册采用这里配置的别名
            }
        },
        // 模块配置
        modules:{
            // 模块UMI对应实现文件的映射表
            // 同时完成模块的组合
        }
    });
});
```

#### 打包发布

打包发布内容详见[NEJ工具集](https://github.com/genify/toolkit)相关文档

### 系统变更

当系统需求变化而进行模块变更我们只需要开发新的模块或删除模块配置即可

#### 新增模块

如果增加一个全新的模块则只需按照上面的逻辑实现步骤开发一个模块即可

如果新增的模块功能在系统中已经实现,则只需修改配置即可,如上例中我们需要在将日志管理下的标签模块在博客设置中也加一份,访问路径为/m/setting/tag/

![新增模块](http://img0.ph.126.net/s4pv6FZmPal8Jx0m0eNltA==/6619471216560468933.png)

修改规则配置

```javascript
rules:{
    // ...
    alias:{
        // ...
        'blog-tag':['/m/blog/tag/','/m/setting/tag/']
    }
}
```

修改模块配置

```javascript
modules:{
    // ...
    '/m/setting/tag/':'module/blog/tag/index.html'
}
```

如果要在/?/setting/tab模块的结构模板中增加一个标签即可

```html
<textarea name="txt" id="module-id-8">
  <div class="ma-t w-tab f-cb">
    <a class="itm fl" href="#/setting/account/" data-id="/setting/account/">账号管理</a>
    <a class="itm fl" href="#/setting/permission/" data-id="/setting/permission/">权限设置</a>
    <a class="itm fl" href="#/setting/tag/" data-id="/setting/tag/">日志标签</a>
  </div>
</textarea>
```

#### 删除模块

将退化的模块从系统中删除只需要将模块对应的UMI配置从模块配置中删除即可,而无需修改具体业务逻辑




================================================
FILE: doc/guide/Build.Scalable.Web.System/Platform.md
================================================
# 构建高可伸缩性的WEB交互式系统(1) - 平台的可伸缩性

## 概述

可伸缩性是一种对软件系统处理能力的设计指标,高可伸缩性代表一种弹性,在系统扩展过程中,能够保证旺盛的生命力,通过很少的改动,就能实现整个系统处理能力的增长。

在系统设计的时候,充分地考虑系统的可伸缩性,一方面能够极大地减少日后的维护开销,并帮助决策者对于投资所能获得的回报进行更加精准的估计;另一方面,高可伸缩性的系统往往会具有更好的容灾能力,从而提供更好的用户体验。

WEB交互式系统的可伸缩性主要体现在两个方面:

* 平台的可伸缩性:随着WEB技术的发展,越来越多的平台开始使用WEB技术来构建系统,一方面不同的平台提供的环境支持存在着各种差异;另一方面随着平台的发展,不断的会有一些旧平台退出历史舞台,新平台转而成为主流平台;因此构建的WEB系统需要能够快速的响应此类变化就需要其具备良好的平台伸缩性
* 模块的可伸缩性:随着系统功能不断增删更新需求的变化,系统可能会变得越来越复杂,冗余信息也可能会越来越多,改动所带来的影响范围也可能会越来越大,因此良好的模块伸缩性可保证系统具有良好的可维护性,让系统始终处于最佳状态

WEB交互式系统的主要应用包括:

* 桌面端/移动端网站类系统(如 [网易云课堂](http://study.163.com/)、[Lofter移动WEB版](http://www.lofter.com/)等)
* 移动混合应用(如 [网易云相册IPad版](http://photo.163.com/cloudphotos/)、[Lofter](http://www.lofter.com/app)等)
* 桌面混合应用(如 [网易云音乐PC版](http://music.163.com/#/download)、[网易邮箱助手](http://mailease.163.com/)等)

## 平台的可伸缩性

WEB交互式系统对平台的可伸缩性主要表现为:

* 可扩展性:对于新兴平台能够快速进行支持
* 可缩减性:对于过时的平台冗余信息能够以最小的修改方式剔除

首先我们先介绍一下WEB交互式系统的目标平台的情况

### 平台分类

根据系统所在容器的差异将平台分为浏览器平台和混合应用平台两大类,各分类的详细说明见下文所述

#### 浏览器平台

##### 按引擎划分

浏览器平台按照主流引擎可以划分为以下几类:

![按引擎划分平台](http://img0.ph.126.net/gmJEDK7pAdfE8nH4VQexkA==/6608273790144412074.png)

| 引擎 | 说明 |
| :--- | :--- |
| Trident | 由微软研发的排版引擎,代表浏览器有Internet Explorer |
| Webkit  | 由Apple、Google、Adobe等公司推动的开源的排版引擎,代表浏览器有Apple Safari、Google Chrome |
| Gecko   | 由Mozilla基金会支持的开源排版引擎,代表浏览器有Mozilla Firefox |
| Presto  | 由Opera Software研发的商用排版引擎,代表浏览器有Opera,由于Opera从15以后就开始采用新的Blink引擎,因此Presto也将逐步淡出我们的目标平台 |
| Blink   | 由Google和Opera Software基于Webkit引擎研发的排版引擎,代表浏览器有Chrome 28+、Opera 15+ |

##### 按功能划分

各引擎的浏览器版本根据对标准、规范的支持程度进行划分可分为以下几类:

![按功能划分平台](http://img1.ph.126.net/PELRvZvw_Gx7DTN3v7hUFQ==/6608921402492080696.png)

由于目前国内基于Trident的Internet Explorer浏览器还占有大量的市场份额,包括低版本的Internet Explorer浏览器,因此我们将浏览器分成三个等级

| 标准性 | 说明 |
| :--- | :--- |
| 差 | 主要针对低版本的Trident引擎(如IE6浏览器)平台,这部分平台对规范和标准的支持程度比较差,在适配时需要做大量额外的适配工作来实现相应的功能,因此如果产品的目标平台定位需要支持此平台则会有一定的性能损耗 |
| 中 | 主要针对中间版本的Trident引擎(如IE7-9浏览器)平台,这部分平台对规范和标准有一定的支持,但是也存在若干功能需要做额外的适配工作来实现 |
| 好 | 主要针对对规范、标准支持比较好的平台,按照标准实现的功能无需做额外的适配工作,因此如果产品的目标平台定位为此平台将取得比较好的用户体验和性能,如移动产品、混合应用等 |

#### 混合应用平台

根据混合应用的宿主平台的差异我们将混合应用的目标平台分为以下几类:

![混合平台划分](http://img1.ph.126.net/1VV98yGBpj3E2KPaa9M3jg==/6608853232771155778.png)

| 宿主     | 说明 |
| :---     | :--- |
| Android  | Android系统的混合应用,浏览器引擎会自动适配至Webkit |
| IOS      | IOS系统的混合应用,浏览器引擎会自动适配至Webkit |
| WinPhone | Windows Phone系统的混合应用,浏览器引擎会自动适配至Trident |
| PC       | 桌面应用,采用CEF做为容器,浏览器引擎会自动适配至Webkit |

### 平台适配

AOP(Aspect-Oriented Programming):面向切面的编程范式,其核心思想是将横切关注点从主关注点中分离出来,因此特定领域的问题代码可以从标准业务逻辑中分离出来,从而使得主业务逻辑和领域性业务逻辑之间不会存在任何耦合性。

这里我们可以借鉴AOP思想来实现平台的适配策略,结合不同的平台实现逻辑,我们可以认为对于使用规范、标准来实现业务逻辑的部分为我们的主关注点,而不同平台可以做为若干的切面关注点进行封装,各平台只需关注自己平台下对标准的修正逻辑即可,因此可以通过增加、删除平台修正的切面逻辑来实现对不同平台的适配。

实现时我们首先提取标准业务逻辑,然后各平台根据实际情况实现对业务逻辑的修正

![AOP策略](http://img2.ph.126.net/27YJvuHJvtvBFv4FoYX7QA==/6608822446445580374.png)

* 标准业务逻辑:主关注点,这里主要是使用根据W3C、ES标准来实现的业务逻辑
* 前置平台修正逻辑:领域特定关注点,主要是根据平台特性对标准在该平台下的修正,修正逻辑会先于标准逻辑执行
* 后置平台修正逻辑:同前置平台修正逻辑,也是领域特定关注点,修正逻辑会在标准逻辑执行后再执行

根据此思路我们对比以下两段代码:

代码一:目前常用的平台适配方式

```javascript
function doSomething(){
    if(isTrident){
        // TODO trident implement
    }else if(isWebkit){
        // TODO webkit implement
    }else if(isGecko){
        // TODO gecko implement
    }else if(isPresto){
        // TODO presto implement
    }else{
        // TODO w3c implement
    }
}

// 上层应用使用
doSomething(1,2,3);
```

此方式对所有平台的修正逻辑均在主逻辑中实现,存在以下弊端:

* 对平台特有的修正逻辑耦合在主逻辑中,平台特有的更新必然引起主逻辑的更新
* 对于新增或删除平台的支持必须修改到主业务逻辑
* 无法分离不必要的平台修正,比如基于webkit引擎的移动平台应用不需要其他平台的修正逻辑

代码二:借鉴AOP思想的平台适配方式

```javascript
function doSomething(){
    // TODO w3c/es implement
}

// 上层应用使用
doSomething(1,2,3);
```

针对Trident平台适配的逻辑,比如 trident.js中

```javascript
// trident implement
doSomething = doSomething._$aop(
    function(_event){
        // TODO trident implement
    },
    function(_event){
        // TODO trident implement
    }
);
```

对比代码一,我们可以发现借鉴AOP思想的接口适配方式分离了标准业务逻辑和平台特有业务逻辑,是否增加平台特有业务逻辑并不会影响主业务逻辑的执行,而对于平台修正逻辑的切入则可以直接通过配置的方式灵活的进行增删,因此我们可以从中得到以下好处:

* 主逻辑和平台特有逻辑无耦合性,可随意分离、整合
* 对于新增平台适配只需新加平台特有逻辑即可,而无需影响到主业务逻辑
* 可通过配置控制支持的目标平台,有选择性的导出平台特有业务逻辑

### 实现举例

[NEJ框架](https://github.com/NetEaseWD/NEJ)借鉴AOP思想提供了配置式的平台适配系统,对于这部分的详细信息可参阅NEJ的《[依赖管理系统](../DEPENDENCY.md)》和《[平台适配系统](../PLATFORM.md)》了解更为详细的信息,以下选取其中相关原理部分进行举例说明

#### platform

控件依赖补丁名称为“platform”,只用于文件依赖,使用“{platform}xxx.js”来表示控件依赖的补丁文件,会解析为依赖xxx.js和xxx.patch.js两个文件。xxx.js为W3C/ES规范实现方式,提供所有标准平台支持的公用部分,xxx.patch.js通过NEJ.patch接口提供不同平台对这些接口的特有实现逻辑

一个典型的适配控件结构如下图所示

![适配控件结构](http://img1.ph.126.net/shaaEm3oj13JZcdMF9iHTQ==/6608208918958379461.png)

这里的widget.js是控件业务逻辑实现文件,在此控件的实现中会依赖到存在平台差异的API,其依赖代码如下所示

```javascript
NEJ.define([
    'util/event',
    '{platform}api.js'
],function(t,h,p){

    // TODO

});
```

这里对 {platform}api.js 的处理方式如下图所示,这里的./相对于当前的代码文件即widget.js文件所在的目录

![platform](http://img2.ph.126.net/ygcxx2DAXf5YbpQ8No7SWg==/6608689405538619068.png)

这里的api.js文件为需平他适配API的标准实现逻辑,而api.patch.js文件则利用NEJ.patch接口对各平台做按需适配逻辑,同时打包时也根据NEJ.patch接口中对平台的条件识别做按需输出,由于api.patch.js文件最终会按需输出,因此在此文件中除了使用NEJ.patch做平台适配逻辑外不允许包含其它业务逻辑

#### NEJ.patch

NEJ框架中的平台按需适配采用NEJ.patch接口来实现,由于打包发布后NEJ.patch相关的接口会被分离出来不会发布上线,因此仅允许在patch文件中调用此接口,平台引擎标识说明如下

| 标识  | 说明 |
| :---  | :--- |
| T     | Trident引擎,如IE |
| W     | Webkit引擎,如chrome |
| G     | Gecko引擎,如firefox |

内置的Trident引擎版本对应的IE版本关系

| Trident版本 | IE版本 |
| :---  | :--- |
| 2.0 | 6 |
| 3.0 | 7 |
| 4.0 | 8 |
| 5.0 | 9 |
| 6.0 | 10 |
| 7.0 | 11 |

接口说明

|      | 类型 | 描述 |
| :--- | :--- | :--- |
| 输入 | String   |   必须,平台的判断条件,如2=<TR<4 |
|      | Array    | 可选,依赖文件列表,规则同define接口定义的文件路径 |
|      | Function | 可选,当前条件下需要执行的脚本 |
| 输出 | Void     | 无 |

```javascript

// 此文件只能定义NEJ.patch不可执行其他业务逻辑
// 打包输出时仅根据平台配置输出所需处理逻辑
// 实际情况看需求,可将平台相关部分逻辑独立到单独的模块中

NEJ.define([
    './hack.js'
],function(h){
    // 针对trident平台的处理逻辑
    NEJ.patch('TR',function(){
        // TODO
    });

    // 针对gecko平台的处理逻辑
    NEJ.patch('GR',[
        './hack.firefox.js'
    ],function(fh){
        // TODO
    });

    // 针对IE6平台的处理逻辑
    NEJ.patch('TR==2.0',['./hack.ie6.js']);

    // 针对IE7-IE9的处理逻辑
    NEJ.patch('3.0<=TR<=5.0',function(){
        // TODO
    });

    // 这里必须同hack.js文件的返回值一致
    return h;
});
```

#### 平台配置

平台参数在开发及打包过程中都会使用,框架支持平台参数的配置通过define.js路径上查询串中的p参数输入。

平台配置信息,此配置又分两类基本配置:补丁配置和混合配置,因为混合模式下使用的浏览器引擎固定,因此当配置中出现混合类型的配置时忽略补丁配置的值。

如果在引入依赖定义库时未指定平台信息则表示系统需对全平台浏览器支持。

##### 补丁配置

主要用来修正浏览器平台对接口及控件的支持,按照目前浏览器引擎划分,参数值由一个或者多个平台标识组成,标识支持如下所示:

| 标识  | 说明 |
| :---  | :--- |
| gk    | 以gecko为核心的浏览器平台,如firefox等 |
| wk    | 以webkit为核心的浏览器平台,如chrome、safari等 |
| td    | 以trident为核心的浏览器平台,如IE、360、maxthon等 |
| td-0  | 以trident为核心的浏览器平台,且引擎内核版本大于等于3.0,即IE>=7 |
| td-1  | 以trident为核心的浏览器平台,且引擎内核版本大于等于7.0,即IE>=10 |

```html
<script src="/path/to/nej/define.js?p=wk|gk|td"></script>
```

##### 混合配置

主要用于混合开发模式下对native接口的适配,按照native平台划分,参数值由一个标识组成,多个标识则以识别的第一个标识为准,标识支持如下所示:

| 标识 | 说明 |
| :--- | :--- |
| cef       | 基于cef框架混合应用,主要针对桌面应用 |
| ios       | ios平台混合应用,如iphone应用、ipod应用、ipad应用等 |
| win       | windows phone平台混合应用 |
| android   | android平台混合应用 |

```html
<script src="/path/to/nej/define.js?p=cef"></script>
```

### 平台变更

通过以上实现举例我们可以看到当平台发生变更时我们可以快速进行扩展或缩减

#### 平台扩展

当有新平台需要作为系统目标平台时,我们只需要做以下工作:

* 增加平台配置识别符,如nxw
* 识别该平台与标准存在的差异,增加平台特有业务逻辑至patch
* 系统对平台配置部分增加新添的识别符,如

    原平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk|gk|td"></script>
    ```

    新增平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk|gk|td|nxw"></script>
    ```

即可完成对平台的扩展,而不会影响到原有的业务逻辑

#### 平台缩减

当系统适配的目标平台由于某种原因退出历史舞台时,系统也需要将该平台的冗余代码从系统中剔除,我们只需要做以下工作:

* 系统对平台配置部分删除要剔除的平台标识,如

    原平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk|gk|td"></script>
    ```

    缩减后平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk"></script>
    ```

即可完成对平台的缩减,而无需修改任何业务逻辑




================================================
FILE: doc/guide/Build.Scalable.Web.System.md
================================================
# 构建高可伸缩性的WEB交互式系统

## 概述

可伸缩性是一种对软件系统处理能力的设计指标,高可伸缩性代表一种弹性,在系统扩展过程中,能够保证旺盛的生命力,通过很少的改动,就能实现整个系统处理能力的增长。

在系统设计的时候,充分地考虑系统的可伸缩性,一方面能够极大地减少日后的维护开销,并帮助决策者对于投资所能获得的回报进行更加精准的估计;另一方面,高可伸缩性的系统往往会具有更好的容灾能力,从而提供更好的用户体验。

WEB交互式系统的可伸缩性主要体现在两个方面:

* 平台的可伸缩性:随着WEB技术的发展,越来越多的平台开始使用WEB技术来构建系统,一方面不同的平台提供的环境支持存在着各种差异;另一方面随着平台的发展,不断的会有一些旧平台退出历史舞台,新平台转而成为主流平台;因此构建的WEB系统需要能够快速的响应此类变化就需要其具备良好的平台伸缩性
* 模块的可伸缩性:随着系统功能不断增删更新需求的变化,系统可能会变得越来越复杂,冗余信息也可能会越来越多,改动所带来的影响范围也可能会越来越大,因此良好的模块伸缩性可保证系统具有良好的可维护性,让系统始终处于最佳状态

WEB交互式系统的主要应用包括:

* 桌面端/移动端网站类系统(如 [网易云课堂](http://study.163.com/)、[易信WebIM](http://web.yixin.im/)、[Lofter移动WEB版](http://www.lofter.com/)等)
* 移动混合应用(如 [网易云相册IPad版](http://photo.163.com/cloudphotos/)、[Lofter](http://www.lofter.com/app)等)
* 桌面混合应用(如 [网易云音乐PC版](http://music.163.com/#/download)、[网易邮箱助手](http://mailease.163.com/)等)

## 平台的可伸缩性

WEB交互式系统对平台的可伸缩性主要表现为:

* 可扩展性:对于新兴平台能够快速进行支持
* 可缩减性:对于过时的平台冗余信息能够以最小的修改方式剔除

首先我们先介绍一下WEB交互式系统的目标平台的情况

### 平台分类

根据系统所在容器的差异将平台分为浏览器平台和混合应用平台两大类,各分类的详细说明见下文所述

#### 浏览器平台

##### 按引擎划分

浏览器平台按照主流引擎可以划分为以下几类:

![按引擎划分平台](http://img0.ph.126.net/gmJEDK7pAdfE8nH4VQexkA==/6608273790144412074.png)

| 引擎 | 说明 |
| :--- | :--- |
| Trident | 由微软研发的排版引擎,代表浏览器有Internet Explorer |
| Webkit  | 由Apple、Google、Adobe等公司推动的开源的排版引擎,代表浏览器有Apple Safari、Google Chrome |
| Gecko   | 由Mozilla基金会支持的开源排版引擎,代表浏览器有Mozilla Firefox |
| Presto  | 由Opera Software研发的商用排版引擎,代表浏览器有Opera,由于Opera从15以后就开始采用新的Blink引擎,因此Presto也将逐步淡出我们的目标平台 |
| Blink   | 由Google和Opera Software基于Webkit引擎研发的排版引擎,代表浏览器有Chrome 28+、Opera 15+ |

##### 按功能划分

各引擎的浏览器版本根据对标准、规范的支持程度进行划分可分为以下几类:

![按功能划分平台](http://img1.ph.126.net/PELRvZvw_Gx7DTN3v7hUFQ==/6608921402492080696.png)

由于目前国内基于Trident的Internet Explorer浏览器还占有大量的市场份额,包括低版本的Internet Explorer浏览器,因此我们将浏览器分成三个等级

| 标准性 | 说明 |
| :--- | :--- |
| 差 | 主要针对低版本的Trident引擎(如IE6浏览器)平台,这部分平台对规范和标准的支持程度比较差,在适配时需要做大量额外的适配工作来实现相应的功能,因此如果产品的目标平台定位需要支持此平台则会有一定的性能损耗 |
| 中 | 主要针对中间版本的Trident引擎(如IE7-9浏览器)平台,这部分平台对规范和标准有一定的支持,但是也存在若干功能需要做额外的适配工作来实现 |
| 好 | 主要针对对规范、标准支持比较好的平台,按照标准实现的功能无需做额外的适配工作,因此如果产品的目标平台定位为此平台将取得比较好的用户体验和性能,如移动产品、混合应用等 |

#### 混合应用平台

根据混合应用的宿主平台的差异我们将混合应用的目标平台分为以下几类:

![混合平台划分](http://img1.ph.126.net/1VV98yGBpj3E2KPaa9M3jg==/6608853232771155778.png)

| 宿主     | 说明 |
| :---     | :--- |
| Android  | Android系统的混合应用,浏览器引擎会自动适配至Webkit |
| IOS      | IOS系统的混合应用,浏览器引擎会自动适配至Webkit |
| WinPhone | Windows Phone系统的混合应用,浏览器引擎会自动适配至Trident |
| PC       | 桌面应用,采用CEF做为容器,浏览器引擎会自动适配至Webkit |

### 平台适配

AOP(Aspect-Oriented Programming):面向切面的编程范式,其核心思想是将横切关注点从主关注点中分离出来,因此特定领域的问题代码可以从标准业务逻辑中分离出来,从而使得主业务逻辑和领域性业务逻辑之间不会存在任何耦合性。

这里我们可以借鉴AOP思想来实现平台的适配策略,结合不同的平台实现逻辑,我们可以认为对于使用规范、标准来实现业务逻辑的部分为我们的主关注点,而不同平台可以做为若干的切面关注点进行封装,各平台只需关注自己平台下对标准的修正逻辑即可,因此可以通过增加、删除平台修正的切面逻辑来实现对不同平台的适配。

实现时我们首先提取标准业务逻辑,然后各平台根据实际情况实现对业务逻辑的修正

![AOP策略](http://img2.ph.126.net/27YJvuHJvtvBFv4FoYX7QA==/6608822446445580374.png)

* 标准业务逻辑:主关注点,这里主要是使用根据W3C、ES标准来实现的业务逻辑
* 前置平台修正逻辑:领域特定关注点,主要是根据平台特性对标准在该平台下的修正,修正逻辑会先于标准逻辑执行
* 后置平台修正逻辑:同前置平台修正逻辑,也是领域特定关注点,修正逻辑会在标准逻辑执行后再执行

根据此思路我们对比以下两段代码:

代码一:目前常用的平台适配方式

```javascript
function doSomething(){
    if(isTrident){
        // TODO trident implement
    }else if(isWebkit){
        // TODO webkit implement
    }else if(isGecko){
        // TODO gecko implement
    }else if(isPresto){
        // TODO presto implement
    }else{
        // TODO w3c implement
    }
}

// 上层应用使用
doSomething(1,2,3);
```

此方式对所有平台的修正逻辑均在主逻辑中实现,存在以下弊端:

* 对平台特有的修正逻辑耦合在主逻辑中,平台特有的更新必然引起主逻辑的更新
* 对于新增或删除平台的支持必须修改到主业务逻辑
* 无法分离不必要的平台修正,比如基于webkit引擎的移动平台应用不需要其他平台的修正逻辑

代码二:借鉴AOP思想的平台适配方式

```javascript
function doSomething(){
    // TODO w3c/es implement
}

// 上层应用使用
doSomething(1,2,3);
```

针对Trident平台适配的逻辑,比如 trident.js中

```javascript
// trident implement
doSomething = doSomething._$aop(
    function(_event){
        // TODO trident implement
    },
    function(_event){
        // TODO trident implement
    }
);
```

对比代码一,我们可以发现借鉴AOP思想的接口适配方式分离了标准业务逻辑和平台特有业务逻辑,是否增加平台特有业务逻辑并不会影响主业务逻辑的执行,而对于平台修正逻辑的切入则可以直接通过配置的方式灵活的进行增删,因此我们可以从中得到以下好处:

* 主逻辑和平台特有逻辑无耦合性,可随意分离、整合
* 对于新增平台适配只需新加平台特有逻辑即可,而无需影响到主业务逻辑
* 可通过配置控制支持的目标平台,有选择性的导出平台特有业务逻辑

### 实现举例

[NEJ框架](https://github.com/NetEaseWD/NEJ)借鉴AOP思想提供了配置式的平台适配系统,对于这部分的详细信息可参阅NEJ的《[依赖管理系统](https://github.com/genify/nej/blob/master/doc/DEPENDENCY.md)》和《[平台适配系统](https://github.com/genify/nej/blob/master/doc/PLATFORM.md)》了解更为详细的信息,以下仅举例说明NEJ中适配的使用方式

一个典型的适配控件结构如下图所示

![适配控件结构](http://img1.ph.126.net/shaaEm3oj13JZcdMF9iHTQ==/6608208918958379461.png)

这里的widget.js是控件业务逻辑实现文件,在此控件的实现中会依赖到存在平台差异的API,其依赖代码如下所示

```javascript
NEJ.define([
    'util/event',
    '{platform}api.js'
],function(t,h,p){

    // TODO

});
```

这里对 {platform}api.js 的处理方式如下图所示,这里的./相对于当前的代码文件即widget.js文件所在的目录

![platform](http://img2.ph.126.net/ygcxx2DAXf5YbpQ8No7SWg==/6608689405538619068.png)

这里的api.js文件为需平他适配API的标准实现逻辑,而api.patch.js文件则利用NEJ.patch接口对各平台做按需适配逻辑,同时打包时也根据NEJ.patch接口中对平台的条件识别做按需输出,由于api.patch.js文件最终会按需输出,因此在此文件中除了使用NEJ.patch做平台适配逻辑外不允许包含其它业务逻辑

```javascript

// 此文件只能定义NEJ.patch不可执行其他业务逻辑
// 打包输出时仅根据平台配置输出所需处理逻辑
// 实际情况看需求,可将平台相关部分逻辑独立到单独的模块中

NEJ.define([
    './hack.js'
],function(h){
    // 针对trident平台的处理逻辑
    NEJ.patch('TR',function(){
        // TODO
    });

    // 针对gecko平台的处理逻辑
    NEJ.patch('GR',[
        './hack.firefox.js'
    ],function(fh){
        // TODO
    });

    // 针对IE6平台的处理逻辑
    NEJ.patch('TR==2.0',['./hack.ie6.js']);

    // 针对IE7-IE9的处理逻辑
    NEJ.patch('3.0<=TR<=5.0',function(){
        // TODO
    });

    // 这里必须同hack.js文件的返回值一致
    return h;
});
```

最后我们只需要配置产品的目标平台即可输出平台对应的适配,而不会存在其他平台的额外影响

```html
<script src="/path/to/nej/define.js?p=wk|gk|td"></script>
```

```html
<script src="/path/to/nej/define.js?p=cef"></script>
```

### 平台变更

通过以上实现举例我们可以看到当平台发生变更时我们可以快速进行扩展或缩减

#### 平台扩展

当有新平台需要作为系统目标平台时,我们只需要做以下工作:

* 增加平台配置识别符,如nxw
* 识别该平台与标准存在的差异,增加平台特有业务逻辑至patch
* 系统对平台配置部分增加新添的识别符,如

    原平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk|gk|td"></script>
    ```

    新增平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk|gk|td|nxw"></script>
    ```

即可完成对平台的扩展,而不会影响到原有的业务逻辑

#### 平台缩减

当系统适配的目标平台由于某种原因退出历史舞台时,系统也需要将该平台的冗余代码从系统中剔除,我们只需要做以下工作:

* 系统对平台配置部分删除要剔除的平台标识,如

    原平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk|gk|td"></script>
    ```

    缩减后平台适配
    ```html
    <script src="/path/to/nej/define.js?p=wk"></script>
    ```

即可完成对平台的缩减,而无需修改任何业务逻辑

## 模块的可伸缩性

WEB交互式系统对模块的可伸缩性同样表现为:

* 可扩展性:对于系统新增的功能需求能够快速响应支持
* 可缩减性:对于系统退化的模块能够以最小的修改方式剔除

这里我们提供一套模块调度的系统架构模式用于支持单页富应用系统的设计架构、模块拆分、模块重组、调度管理等功能

### 模块

这里我们定义的模块是指从系统中拆分出来的可与用户进行交互完成一部分完整功能的独立单元

#### 模块组成

因为这里描述的模块可独立与用户完成交互功能,因此模块会包含以下元素

* 样式:定义模块的效果
* 结构:定义模块的结构
* 逻辑:实现模块的功能

以上元素对于一个WEB系统开发者来说并不陌生,而我们只需要寻求一种形式将这些内容封装起来即可

#### 模块封装

从模块的组成我们可以看到系统中分离出来的模块可能会长成这个样子,比如module.html就是我们分离出来的一个模块

当然这里也可以用脚本文件封装,样式和结构采用注入形式,我们这里以html文件封装举例

```html
<!-- 模块样式 -->
<style>
    .m-mdl-1 .a{color:#aaa;}
    .m-mdl-1 .b{color:#bbb;}

    /* 此处省略若干内容 */
</style>

<!-- 模块结构 -->
<div class="m-mdl-1">
  <p class="a">aaaaaaaaaaaaaaaaaaa</p>
  <p class="b">bbbbbbbbbbbbbbbbbbb</p>

  <!-- 此处省略若干内容 -->
</div>

<!-- 模块逻辑 -->
<script>
    (function(){
        var a = 'aaa';
        var b = 'bbb';

        // 此处省略若干内容
    })();
</script>
```

而这个模块在用户需要时加载到客户端,并展现出来跟用户进行交互,完成功能,但是我们会发现如果系统预加载了此模块或者模块在parse时这些内容会被直接执行,而这个结果并不是我们需要的,因此我们需要将模块的各元素文本化处理,文本化处理有多种方式,如作为文本script、textarea等标签内容,因此module.html里的模块我们可以封装成如下样子,以textarea举例:

```html
<!-- 模块样式 -->
<textarea name="css">
    .m-mdl-1 .a{color:#aaa;}
    .m-mdl-1 .b{color:#bbb;}

    /* 此处省略若干内容 */
</textarea>

<!-- 模块结构 -->
<textarea name="html">
    <div class="m-mdl-1">
      <p class="a">aaaaaaaaaaaaaaaaaaa</p>
      <p class="b">bbbbbbbbbbbbbbbbbbb</p>

      <!-- 此处省略若干内容 -->
    </div>
</textarea>

<!-- 模块逻辑 -->
<textarea name="js">
    (function(){
        var a = 'aaa';
        var b = 'bbb';

        // 此处省略若干内容
    })();
</textarea>
```

### 管理依赖

从系统中拆分出来的模块之间是存在有一定关系的,如一个模块的呈现必须依赖另外一个模块的呈现,下面我们会以一个简单的例子来讲解模块之间的依赖管理,如下图是我们的一个单页应用系统

![单页富应用范例](http://nej.netease.com/images/sample.gif)

从上图不难看出整个系统包含以下几部分内容:

* 日志管理

  * 日志:日志列表,可切换收件箱/草稿箱/回收站/标签
  * 标签:标签列表,可转至日志按标签查看列表

* 博客设置

  * 账号管理

    * 基本资料:用户基本资料设置表单
    * 个人经历:个人经历填写表单

  * 权限设置:权限设置表单

而这些模块之间的层级关系则如下所示

![模块层级结构图](http://img0.ph.126.net/7ixXaTU-uFtbe0pKCWCgPA==/6597270977286264717.png)

针对交互式系统的这种层级架构典型的模式可以参阅

* [PAC(Presentation-Abstraction-Control)模式](http://en.wikipedia.org/wiki/Presentation%E2%80%93abstraction%E2%80%93control)
* [HMVC(Hierarchical model–view–controller)模式](http://en.wikipedia.org/wiki/Hierarchical_model%E2%80%93view%E2%80%93controller)

然而在WEB交互式系统的实践过程中我们发现这种模式会存在一些缺陷:

* 由于每个父模块自己维护了所有的子模块,因此父子模块之间耦合性过强,父模块必须耦合所有子模块
* 由于模块之间不能直接越级调用,因此子模块需要其他模块协助时必须层层向上传递事件,如果层级过深则会影响到系统效率
* 模块的增删等变化导致的变更涉及的影响较大,删除中间节点上的模块可能导致相邻的若干模块的变更
* 多人协作开发系统时存在依赖关系的模块会导致开发人员之间的紧密耦合

在这里我们给出了一种基于模块标识的依赖管理配置方案,可以彻底的将模块进行解耦,每个模块可以独立完整的完成自己的交互功能,而系统的整合则可以通过配置的方式灵活的重组各模块,模块的增删操作只需修改配置即可完成,而无需影响到具体业务逻辑

下文我们会通过以上例子来讲解此方案的原理和实际操作方式

#### 模块标识

因为本方案会基于模块标识做配置,因此在介绍方案之前我们先介绍一下模块标识,这里我们给模块标识取名为**UMI**(Uniform Module Identifier)统一模块标识,下文简称UMI,遵循以下规则约定

* 格式同URI的Path部分,如 /m/m0/
* 必须以“/”符开始
* 私有模块必须以“/?”开始
* 承载模块的依赖关系,如 /m/m0/ 和 /m/m1/ 表明这两个标识对应模块的父模块标识均为 /m

每个UMI均可唯一标识一个模块及模块在系统中的依赖关系,在模块章节我们介绍了一个模块可以用一个html进行封装,因此我们可以得到以下结果

![UMI与模块地址映射关系图](http://img0.ph.126.net/DCSPjxzVgjlgc8B983YSSg==/2673167853921558567.png)

每个UMI均可映射到一个模块实现文件,这样我们就可以将模块从具体实现中解耦出来,对模块的增删修改操作只需调整UMI和模块文件的映射关系即可,而无需涉及具体业务逻辑的修改

#### 模块依赖

在解决了模块与实现分离的问题后,我们接下来需要将层级式的模块扁平化来解耦模块之间的依赖关系

回到前面的例子,模块之间的层级关系如下图所示

![模块层级关系图](http://img1.ph.126.net/NFbfUjokCb1KsI8tCj04xw==/6608203421400487879.jpg)

如果我们将图中的依赖关系进行抽象分离后可以发现所有的模块即可呈现扁平的状态

![模块关系分离图](http://img1.ph.126.net/XByNuSHTyJ_OzmAFIcqGKQ==/6608480498329578217.png)

而对于模块之前的依赖关系的管理在所有系统中都是一致的,但是每个模块的具体功能实现是由系统来决定的,不同的系统是截然不同的,因此本方案提供的解决方案主要是用来维护模块之间的依赖关系的

从上图我们可以比较清楚的看到模块之间的依赖关系呈现树状结构,因此我们会以树的结构来组织维护模块之间的依赖关系,我们称之为依赖关系树,而当我们将这棵树上的任意节点与根节点之间的路径用“/”分隔序列化后发现刚好与我们提供的UMI是匹配的,因此组成系统的模块的UMI可以跟依赖关系树的节点一一对应起来,如下图所示

![依赖关系树节点序列化成UMI](http://img2.ph.126.net/cu36voh3bRXFx530aW6nkg==/6608921402492317376.png)

在模块标识章节我们介绍了UMI与模块封装文件可以相互映射,因此依赖关系树上的节点可以直接与模块的实现文件做一一对应,如下图所示

![依赖关系树映射模块实现文件](http://img1.ph.126.net/6Pfkz1_4dd32jnL60QQ1Kw==/6608598146073751639.png)

至此,我们将垂直层级依赖的模块通过依赖关系树分解成了无任何关系的扁平模块结构

#### 模块组合

模块只需要有个呈现容器即可渲染出来,因此模块如果需要能够做任意组合,只需将模块分成两种类型:提供容器的模块和使用容器的模块即可,当然一个模块可同时兼具提供容器和使用容器的功能,提供容器的模块和使用容器的模块可任意组合

![模块组合](http://img2.ph.126.net/D446SK2cBk_aGZFLVRwIew==/3669307796500606643.png)

对于模块组合的配置代码范例

```javascript
'/m/blog/list/':{
    module:'module/layout/blog.list/index.html',
    composite:{
        box:'/?/blog/box/',
        tag:'/?/blog/tag/',
        list:'/?/blog/list/',
        clazz:'/?/blog/class/'
    }
}
```

### 调度策略

在将模块扁平化后,各模块就可以安排给不同的开发人员进行功能实现和测试了,各模块完成后根据依赖关系树进行系统整合,系统整合后各模块会遵循一定的调度策略进行调度

#### 模块状态

根据模块调度的阶段划分,模块的状态可以分为以下四种:

* 模块构建:构建模块结构
* 模块显示:将模块渲染到指定的容器中
* 模块刷新:根据外界输入的参数信息获取数据并展示(这里主要做数据处理)
* 模块隐藏:模块放至内存中,回收由显示和刷新阶段产生的额外数据及结构

调度策略主要控制模块在这几个阶段之间的转换规则

#### 模块显示

当用户请求显示一个模块时各模块会遵循以下步骤进行调度,假设请求显示 /m/blog/list/ 模块

![显示调度策略](http://img1.ph.126.net/ip4S9-mTCAH943VGwXZ1OQ==/6619563575537201227.png)

1. 检查目标节点到根节点路径上注册的模块,如果注册的是模块的实现文件地址,则请求载入模块实现文件
2. 如果节点所在的模块的所有祖先节点已显示,则当前模块可被显示出来,否则等待祖先模块的显示调度
3. 模块载入后根据第二步骤原则尝试调度目标模块的显示

#### 模块切换

当用户从一个模块切换到另外一个模块时各模块遵循以下步骤调度,假设从 /m/blog/list/ 切换到 /m/setting/account/edu/ 模块

![模块切换](http://img0.ph.126.net/ca9IdYk2ezLL-zN8vRqI4Q==/6619574570653479153.png)

1. 查找源模块与目标模块的公共父节点

   ![查找公共模块](http://img2.ph.126.net/G36CYcFsck1w8Uf0k7oOIw==/6608672912864439374.png)

2. 从源节点到公共节点之间的模块调度隐藏操作

   ![隐藏模块](http://img0.ph.126.net/kCydlPd-q7yUIQ2rc65Eyg==/6608818048399307052.png)

3. 从根节点到公共节点之间的模块调度刷新操作

   ![刷新模块](http://img2.ph.126.net/sN7NPW5etJ0FVjv-TyBL9w==/6608677310910950450.png)

4. 从公共节点到目标节点之间的模块调度显示操作

   ![显示模块](http://img1.ph.126.net/DwnLyinZy9ZG8IX_uwOmsg==/6599306173308730570.png)

### 消息通道

大部分时候我们不建议使用模块之前的消息通信,实践中也存在一些特殊情况会需要模块之前的消息通信,这里提供两种方式的消息通讯

* 点对点的消息:一个模块发送消息时明确指定目标模块的UMI
* 观察订阅消息:一个模块可以对外申明发布了什么样的消息,有需要的模块可以订阅该模块UMI上的消息

### 实例分析

[NEJ框架](https://github.com/NetEaseWD/NEJ)根据以上描述对此套架构模式做了实现,我们用上面的具体实例讲解如何使用NEJ中的模块调度系统来拆分一个复杂系统、开发测试模块、整合系统等

#### 系统分解

##### 绘制层级关系图

当我们拿到一个复杂系统时根据交互稿可以绘制出组成系统的模块的层级关系图,并确定系统对外可访问的模块

![模块层级关系图](http://img1.ph.126.net/UmNMLNzb-Vjdtv8skT8kqg==/6608742182096989774.png)

##### 抽象依赖关系树

从模块的层级关系图中我们可以非常方便的抽象出模块的依赖关系树

![抽象依赖关系树](http://img1.ph.126.net/BK5_LhDhh01addpDJuY4Yw==/6619331578583739475.png)

将抽象出来的依赖关系树根据UMI规则进行格式化,格式化的主要操作包括

* 增加一个名称为“/”的根结点(也可将“m”结点改为“/”)
* 每个结点增加“/”的子节点作为默认节点

![格式化依赖关系树](http://img2.ph.126.net/5b3ByaubY0XFOlnU1PZHAw==/6619382156118617138.png)

至此输出的依赖关系树,具有以下特性:

* 任何一个结点(除根结点外)到根结点路径上的结点名称用“/”分隔组合起来即为结点的UMI值,如list结点的UMI值为/m/blog/list
* 任何结点上的模块都依赖于他祖先结点(注册有模块)上的模块存在,如blog结点和list结点均注册有模块,则list结点上的模块显示必须以blog结点上的模块的显示为先决条件

##### 确定对外模块注册节点

五个对外可访问的模块:日志、标签、基本资料、个人经历、权限设置,在依赖关系树中找到合适的结点(叶子结点,层级关系树在依赖关系树中对应的结点或“/”结点)来注册对外可访问的模块

![对外模块注册节点](http://img0.ph.126.net/vC28JzPSddgzZdpEAeBZ2w==/6619485510211628655.png)

##### 确定布局模块注册节点

从可访问模块注册的结点往根结点遍历,凡碰到两模块交叉的结点即为布局模块注册结点,系统所需的组件相关的模块可注册到根结点,这样任何模块使用的时候都可以保证这些组件已经被载入

![布局模块注册节点](http://img1.ph.126.net/Jjp0GCDfh2eXvaxJvhiCeQ==/6619215030351195733.png)

##### 映射模块功能

原则:结点的公共父结点实现结点上注册的模块的公共功能

举例:blog结点和setting结点的公共父结点为m结点,则我们可以通过切换blog模块和setting模块识别不变的功能即为m模块实现的功能,同理其他模块

![功能映射](http://img2.ph.126.net/64xhHIfGy4qdQKSV5Bym-g==/2679360303409193237.png)

##### 分解复杂模块

进一步分解复杂模块,一般需要分解的模块包括:

* 可共用模块,比如日志列表,可以在日志管理页面呈现,也可以在弹层中显示
* 逻辑上无必然联系的模块,如日志模块中日志列表与右侧的按标签查看的标签列表之间没有必然的联系,任何一个模块的移除或添加都不会影响到另外一个模块的业务逻辑

![分解复杂模块](http://img1.ph.126.net/wRM1uAxQDRFhfAzlNw8b5Q==/6599274287471525959.png)

至此我们可以得到两棵系统分解后的依赖关系树

对外模块依赖关系树

![对外模块依赖关系树](http://img2.ph.126.net/6yAKfH-sST3-RGc0xIxU2g==/6608788361585356880.png)

私有模块依赖关系树

![私有模块依赖关系树](http://img1.ph.126.net/KgimU46Zn4ZnUtxiBrXdKw==/6608191326772584502.png)

##### 绘制模块功能规范表

本例中为了说明分解过程将所有可分解的模块都做了分解,实际项目看具体情况,比如这里的/m模块组合的/?/tab/模块的功能可以直接在/m模块中实现,而不需要新建一个/?/tab/模块来实现这个功能

规范表范例如下所示

![功能规范表](http://img1.ph.126.net/e_0ZFwbefuXT5bZ6bq17Rg==/1496320976294650210.png)

#### 构建目录

##### 项目目录

项目目录的构建如下图所示

![项目目录](http://img0.ph.126.net/7mfjeInfGILw-tWQznxxLA==/6608777366469080397.png)

各目录说明

```
webroot                    项目前端开发相关目录
   |- res                  静态资源文件目录,打包时可配置使用到该目录下的静态资源带版本信息
   |- src                  前端源码目录,最终发布时该目录不会部署到线上
       |- html
            |- module      单页面模块目录,系统所有模块的实现均在此目录下
            |- app.html    单页面入口文件
```

##### 模块单元目录

根据模块封装规则一个模块单元由以下几部分组成:

* 模块测试:模块实现的功能可以通过模块测试页面独立进行测试
* 模块结构:模块所涉及的结构分解出来的若干模板集合
* 模块逻辑:根据模块规范实现的模块业务逻辑,从模块基类继承
* 模块样式:模块特有的样式,一般情况下这部分样式可以直接在css目录下实现

结构范例如下所示

![模块单元目录](http://img0.ph.126.net/o6dTV8RpqAtS63zxdz8sEg==/6608917004445806742.png)

至此我们可以得到所有模块的目录结构如下所示

![模块目录结构](http://img0.ph.126.net/2cMlaKsMBzOfAPPdH9IzkA==/6608797157678379135.png)

#### 模块实现

##### 结构

这里我们假设系统的静态页面已经做完,这里的模块实现只是在原有结构的基础上进行结构分解和业务逻辑的实现,结构部分内容主要将模块相关的静态结构拆分成若干NEJ的模板,注意:

* 模板中的外联资源如css,js文件地址如果使用的是相对路径则均相对于模块的html文件路径
* 模板集合中的外联资源必须使用@TEMPLATE标记标识,这个在后面打包发布章节会详细介绍

NEJ模板说明

![NEJ模板](http://img2.ph.126.net/W_RhwYyx4kbPPGMpcVoUwQ==/6608414527633039667.png)

模块结构举例

```html
<meta charset="utf-8"/>

<textarea name="txt" id="m-ifrm-module">
  <div class="n-login">
    <div class="iner j-flag">
      <span class="cls j-flag">×</span>
      <span class="min j-flag">-</span>
    </div>
    <div class="cnt j-cnt"></div>
  </div>
</textarea>


<textarea name="js" data-src="./index.css"></textarea>
<textarea name="js" data-src="./index.js"></textarea>

```

##### 逻辑

依赖util/dispatcher/module模块,从_$$ModuleAbstract扩展一个项目的模块基类,完成项目中模块特有属性、行为的抽象

```javascript
/*
 * ------------------------------------------
 * 项目模块基类实现文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
NEJ.define([
    'base/klass',
    'util/dispatcher/module'
],function(_k,_t,_p){
    // variable declaration
    var _pro;
    /**
     * 项目模块基类对象
     * @class   {_$$Module}
     * @extends {_$$ModuleAbstract}
     * @param   {Object}  可选配置参数,已处理参数列表如下所示
     */
    _p._$$Module = _k._$klass();
    _pro = _p._$$Module._$extend(_t._$$ModuleAbstract);
    /**
     * 操作
     * @param  {Object}
     * @return {Void}
     */
    _pro.__doSomething = function(_args){
        // TODO
    };

    // TODO

    return _p;
});
```

根据模块状态的划分,我们在实现一个模块时需要实现以下几个接口

![模块各阶段接口](http://img0.ph.126.net/8XyKVwG3dzG0dK59qteZFw==/6619467918025585071.png)

各阶段对应的接口:

* 构建 - \_\_doBuild:构建模块结构,缓存模块需要使用的节点,初始化组合控件的配置参数
* 显示 - \_\_onShow:将模块放置到指定的容器中,分配组合控件,添加相关事件,执行\_\_onRefresh的业务逻辑
* 刷新 - \_\_onRefresh:根据外界输入的参数信息获取数据并展示(这里主要做数据处理)
* 隐藏 - \_\_onHide:模块放至内存中,回收在\_\_onShow中分配的组合控件和添加的事件,回收\_\_onRefresh中产生的视图(这里尽量保证执行完成后恢复到\_\_doBuild后的状态)

具体模块实现举例

```javascript
/*
 * ------------------------------------------
 * 项目模块实现文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
NEJ.define([
    'base/klass',
    'util/dispatcher/module',
    '/path/to/project/module.js'
],function(_k,_e,_t,_p){
    // variable declaration
    var _pro;
    /**
     * 项目模块对象
     * @class   {_$$ModuleDemo}
     * @extends {_$$Module}
     * @param   {Object} 可选配置参数
     */
    _p._$$ModuleDemo = _k._$klass();
    _pro = _p._$$ModuleDemo._$extend(_t._$$Module);
    /**
     * 构建模块,主要处理以下业务逻辑
     * - 构建模块结构
     * - 缓存后续需要使用的节点
     * - 初始化需要使用的组件的配置信息
     * @return {Void}
     */
    _pro.__doBuild = function(){
        this.__super();
        // TODO
    };
    /**
     * 显示模块,主要处理以下业务逻辑
     * - 添加事件
     * - 分配组件
     * - 处理输入信息
     * @param  {Object} 输入参数
     * @return {Void}
     */
    _pro.__onShow = function(_options){
        this.__super(_options);
        // TODO
    };
    /**
     * 刷新模块,主要处理以下业务逻辑
     * - 分配组件,分配之前需验证
     * - 处理输入信息
     * - 同步状态
     * - 载入数据
     * @return {Void}
     */
    _pro.__onRefresh = function(_options){
        this.__super(_options);
        // TODO
    };
    /**
     * 隐藏模块,主要处理以下业务逻辑
     * - 回收事件
     * - 回收组件
     * - 尽量保证恢复到构建时的状态
     * @return {Void}
     */
    _pro.__onHide = function(){
        this.__super();
        // TODO
    };
    // notify dispatcher
    _e._$regist(
        'umi_or_alias',
        _p._$$ModuleDemo
    );

    return _p;
});
```

##### 消息

###### 点对点消息

模块可以通过\_\_doSendMessage接口向指定UMI的模块发送消息,也可以通过实现\_\_onMessage接口来接收其他模块发给他的消息

发送消息

```javascript
_pro.__doSomething = function(){

    // TODO

    this.__doSendMessage(
        '/m/setting/account/',{
            a:'aaaaaa',
            b:'bbbbbbbbb'
        }
    );
};
```

接收消息

```javascript
_pro.__onMessage = function(_event){
    // _event.from 消息来源
    // _event.data 消息数据,这里可能是 {a:'aaaaaa',b:'bbbbbbbbb'}

    // TODO
};
```

###### 发布订阅消息

发布消息

```javascript
_pro.__doSomething = function(){

    // TODO

    this.__doPublishMessage(
        'onok',{
            a:'aaaaaa',
            b:'bbbbbbbb'
        }
    );
};
```

订阅消息

```javascript
_pro.__doBuild = function(){

    // TODO

    this.__doSubscribeMessage(
        '/m/message/account/','onok',
        this.__onMessageReceive._$bind(this)
    );
};
```

##### 自测

创建html页面,使用模板引入模块实现文件

```html
<!-- template box -->
<div id="template-box" style="display:none;">
  <textarea name="html" data-src="../index.html"></textarea>
</div>
```

模块放至document.mbody指定的容器中

```javascript
NEJ.define([
    'util/dispatcher/test'
],function(_e){
    document.mbody = 'module-id-0';
    // test module
    _e._$testByTemplate('template-box');
});
```

#### 系统整合

##### 映射依赖关系树

系统整合时,我们只需要将依赖关系树中需要注册模块的节点同模块实现文件进行映射即可

对外模块整合

![对外模块整合](http://img2.ph.126.net/CQe6y5Bdgkl4bA5VsSIwzA==/6608893914701623469.png)

私有模块整合

![私有模块整合](http://img0.ph.126.net/upKrOu1fEmK_Q6x2jc-ibA==/6619430534630239416.png)

##### 提取系统配置信息

规则配置举例

```javascript
  rules:{
      rewrite:{
          '404':'/m/blog/list/',
          '/m/blog/list/':'/m/blog/',
          '/m/setting/account/':'/m/setting/'
      },
      title:{
          '/m/blog/tag/':'日志标签',
          '/m/blog/list/':'日志列表',
          '/m/setting/permission/':'权限管理',
          '/m/setting/account/':'基本资料',
          '/m/setting/account/edu/':'教育经历'
      },
      alias:{
          'system-tab':'/?/tab/',
          'blog-tab':'/?/blog/tab/',
          'blog-list-box':'/?/blog/box/',
          'blog-list-tag':'/?/blog/tag/',
          'blog-list-class':'/?/blog/class/',
          'blog-list':'/?/blog/list/',
          'setting-tab':'/?/setting/tab/',
          'setting-account-tab':'/?/setting/account/tab/',

          'layout-system':'/m',
          'layout-blog':'/m/blog',
          'layout-blog-list':'/m/blog/list/',
          'layout-setting':'/m/setting',
          'layout-setting-account':'/m/setting/account',

          'blog-tag':'/m/blog/tag/',
          'setting-edu':'/m/setting/account/edu/',
          'setting-profile':'/m/setting/account/',
          'setting-permission':'/m/setting/permission/'
      }
  }
```

模块配置举例

```javascript
  modules:{
      '/?/tab/':'module/tab/index.html',
      '/?/blog/tab/':'module/blog/tab/index.html',
      '/?/blog/box/':'module/blog/list.box/index.html',
      '/?/blog/tag/':'module/blog/list.tag/index.html',
      '/?/blog/class/':'module/blog/list.class/index.html',
      '/?/blog/list/':'module/blog/list/index.html',
      '/?/setting/tab/':'module/setting/tab/index.html',
      '/?/setting/account/tab/':'module/setting/account.tab/index.html',

      '/m':{
          module:'module/layout/system/index.html',
          composite:{
              tab:'/?/tab/'
          }
      },
      '/m/blog':{
          module:'module/layout/blog/index.html',
          composite:{
              tab:'/?/blog/tab/'
          }
      },
      '/m/blog/list/':{
          module:'module/layout/blog.list/index.html',
          composite:{
              box:'/?/blog/box/',
              tag:'/?/blog/tag/',
              list:'/?/blog/list/',
              clazz:'/?/blog/class/'
          }
      },
      '/m/blog/tag/':'module/blog/tag/index.html',

      '/m/setting':{
          module:'module/layout/setting/index.html',
          composite:{
              tab:'/?/setting/tab/'
          }
      },
      '/m/setting/account':{
          module:'module/layout/setting.account/index.html',
          composite:{
              tab:'/?/setting/account/tab/'
          }
      },
      '/m/setting/account/':'module/setting/profile/index.html',
      '/m/setting/account/edu/':'module/setting/edu/index.html',
      '/m/setting/permission/':'module/setting/permission/index.html'
  }
```

##### 模块组合

模块通过\_\_export属性开放组合模块的容器,\_\_export中的parent为子模块的容器节点,顶层模块(如 “/m”)可以通过重写\_\_doParseParent来明确指定应用所在容器

```javascript
_pro.__doBuild = function(){
    this.__body = _e._$html2node(
        _e._$getTextTemplate('module-id-l2')
    );
    // 0 - box select
    // 1 - class list box
    // 2 - tag list box
    // 3 - sub module box
    var _list = _e._$getByClassName(this.__body,'j-flag');
    this.__export = {
        box:_list[0],
        clazz:_list[1],
        tag:_list[2],
        list:_list[3],
        parent:_list[3]
    };
};
```

通过composite配置模块组合

```javascript
'/m/blog/list/':{
    module:'module/layout/blog.list/index.html',
    composite:{
        box:'/?/blog/box/',
        tag:'/?/blog/tag/',
        list:'/?/blog/list/',
        clazz:'/?/blog/class/'
    }
}
```

模块组合时可以指定组合模块的处理状态
* onshow  - 这里配置的组合模块仅在模块显示时组合,后续的模块refresh操作不会导致组合模块的refresh,适合于模块在显示后不会随外界输入变化而变化的模块
* onrefresh  -  这里配置的模块在模块显示时组合,后续如果模块refresh时也会跟随做refresh操作,适用于组合的模块需要与外部输入同步的模块
* 不指定onshow或者onrefresh的模块等价于onrefresh配置的模块

```javascript
composite:{
    onshow:{
        // 模块onshow时组合
        // 组合的模块在模块onrefresh时不会刷新
    },
    onrefresh{
        // 模块onshow时组合
        // 组合的模块在模块onrefresh时也同时会刷新
    }
    // 这里配置的组合模块等价于onrefresh中配置的模块
}
```

##### 启动应用

根据配置启动应用

```javascript
NEJ.define([
    'util/dispatcher/dispatcher'
],function(_e){
    _e._$startup({
        // 规则配置
        rules:{
            rewrite:{
                // 重写规则配置
            },
            title:{
                // 标题配置
            },
            alias:{
                // 别名配置
                // 建议模块实现文件中的注册采用这里配置的别名
            }
        },
        // 模块配置
        modules:{
            // 模块UMI对应实现文件的映射表
            // 同时完成模块的组合
        }
    });
});
```

#### 打包发布

打包发布内容详见[NEJ工具集](https://github.com/genify/toolkit)相关文档

### 系统变更

当系统需求变化而进行模块变更我们只需要开发新的模块或删除模块配置即可

#### 新增模块

如果增加一个全新的模块则只需按照上面的逻辑实现步骤开发一个模块即可

如果新增的模块功能在系统中已经实现,则只需修改配置即可,如上例中我们需要在将日志管理下的标签模块在博客设置中也加一份,访问路径为/m/setting/tag/

![新增模块](http://img0.ph.126.net/s4pv6FZmPal8Jx0m0eNltA==/6619471216560468933.png)

修改规则配置

```javascript
rules:{
    // ...
    alias:{
        // ...
        'blog-tag':['/m/blog/tag/','/m/setting/tag/']
    }
}
```

修改模块配置

```javascript
modules:{
    // ...
    '/m/setting/tag/':'module/blog/tag/index.html'
}
```

如果要在/?/setting/tab模块的结构模板中增加一个标签即可

```html
<textarea name="txt" id="module-id-8">
  <div class="ma-t w-tab f-cb">
    <a class="itm fl" href="#/setting/account/" data-id="/setting/account/">账号管理</a>
    <a class="itm fl" href="#/setting/permission/" data-id="/setting/permission/">权限设置</a>
    <a class="itm fl" href="#/setting/tag/" data-id="/setting/tag/">日志标签</a>
  </div>
</textarea>
```

#### 删除模块

将退化的模块从系统中删除只需要将模块对应的UMI配置从模块配置中删除即可,而无需修改具体业务逻辑

## 总结

随着WEB技术的快速发展,单页面系统([SPA](http://en.wikipedia.org/wiki/Single-page_application))的应用变得越来越广泛;随着此类系统复杂度的增加,其对平台及模块的伸缩性方面需求变得越来越重要,而对于这两方面业界也有不少解决方案业,本文我们主要探讨了网易[NEJ框架](https://github.com/NetEaseWD/NEJ)在这些方面给出的解决方案;网易在单页面系统方面也做了多年的实践和技术积累,如 近几年的[网易云音乐PC版](http://music.163.com/#/download)、[易信WebIM](https://web.yixin.im/)、[网易邮箱助手](http://mailease.163.com/)等,早些年的 [网易相册](http://photo.163.com/)、[网易邮箱](http://mail.163.com)等,移动端的[网易云相册IPad版](http://photo.163.com/cloudphotos/)、[Lofter Android版](http://www.lofter.com/app)等产品均是此类单页面系统的应用实践,如实践过程中对这方面有兴趣的同学可进一步做交流


================================================
FILE: package.json
================================================
{
    "name": "nej-framework",
    "description": "JavaScript Cross-Platform Framework",
    "version": "0.5.2",
    "license": "MIT",
    "keywords": [
        "nej",
        "javascript",
        "cross platform",
        "framework"
    ],
    "author": "genify <caijf@corp.netease.com>",
    "main": "./src/define.js",
    "homepage": "https://github.com/genify/nej",
    "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
    },
    "repository": {
        "type": "git",
        "url": "https://github.com/genify/nej.git"
    },
    "bugs": {
        "url": "https://github.com/genify/nej/issues"
    }
}

================================================
FILE: res/crossdomain.xml
================================================
<?xml version="1.0"?>
<cross-domain-policy> 
  <site-control permitted-cross-domain-policies="by-content-type"/> 
  <allow-access-from secure="true" domain="*"/> 
</cross-domain-policy>

================================================
FILE: res/nej_proxy_frame.html
================================================
<html> <head> <title>NEJ AJAX PROXY</title> <meta charset="utf-8"/> 
<script>window.whitelist=[/.*/i];</script> 
<script>!function(){var e=[];window.addMsgListener=function(n){e.push(n)};var n=function(n){for(var t=0,r=e.length;t<r;t++)try{e[t].call(null,n)}catch(o){}};if(!window.postMessage){var t="MSG|",r=[];var o=function(){var e=function(e){var n={},t=e.split("|");for(var r=0,o=t.length,a;r<o;r++){a=t[r].split("=");n[decodeURIComponent(a.shift())]=decodeURIComponent(a.join("="))}return n};return function(){var r=unescape(window.name||"");if(r&&0==r.indexOf(t)){window.name="";r=r.replace(t,"");var o=e(r),a=(o.origin||"").toLowerCase();if(!a||"*"==a||0==location.href.toLowerCase().indexOf(a))n({data:o.data,origin:document.referrer})}}}();var a=function(){var e;var n=function(e,n){for(var t=0,r=e.length;t<r;t++)if(e[t]==n)return!0;return!1};return function(){if(r.length){e=[];for(var t=r.length-1,o;t>=0;t--){o=r[t];if(!n(e,o.w)){e.push(o.w);r.splice(t,1);o.w.name=o.d}}e=null}}}();window.doPostMessage=function(){var e=function(e){var n=[];for(var t in e)n.push(encodeURIComponent(t)+"="+encodeURIComponent(e[t]));return n.join("|")};var n=function(n){var r={};n=n||{};r.origin=n.origin||"*";r.ref=location.href;r.data=""+n.data;return t+e(r)};return function(e,t){r.unshift({w:e,d:escape(n(t))})}}();window.setInterval(a,100);window.setInterval(o,50)}else{window.doPostMessage=function(e,n){n=n||{};e.postMessage(""+n.data,n.origin||"*")};if(window.addEventListener)window.addEventListener("message",n,!1);else window.attachEvent("onmessage",n)}}();!function(){var e=function(){var e=document.referrer||"";if(!e||!whitelist.length)return!1;for(var n=0,t=whitelist.length,r;n<t;n++){r=whitelist[n];if(r.test&&r.test(e)||e==r)return!0}return!1};if(e()){var n=function(){};var t=function(e,n){try{n=n.toLowerCase();if(null===e)return"null"==n;if(void 0===e)return"undefined"==n;else return Object.prototype.toString.call(e).toLowerCase()=="[object "+n+"]"}catch(t){return!1}};var r=function(e){var n=[];for(var t in e)if(e.hasOwnProperty(t))n.push(encodeURIComponent(t)+"="+encodeURIComponent(e[t]));else;return n.join("&")};var o=function(){var e=/"/gi,o=["Msxml2.XMLHTTP.6.0","Msxml2.XMLHTTP.3.0"];var a=function(){if(window.XMLHttpRequest)return new XMLHttpRequest;try{for(var e=0,n=o.length;e<n;e++)return new ActiveXObject(o[e])}catch(t){return null}};var i=function(n){var t='NEJ-AJAX-DATA:{                          "key":"'+n.key+'",                          "status":"'+n.status+'",                          "result":"'+encodeURIComponent(n.result)+'"                      }';if(!window.postMessage)t='"'+t.replace(e,'\\"')+'"';window.doPostMessage(parent,{data:t})};return function(e){if(e&&e.url){var o=a(),u=e.key;var s,f=parseInt(e.timeout)||0;if(f)s=window.setTimeout(function(){o.onreadystatechange=n;o.abort();i({key:u,status:-1})},f);o.onreadystatechange=function(){if(4==o.readyState){window.clearTimeout(s);i({key:u,status:o.status,result:o.responseText||""})}};o.open(e.method||"GET",e.url,!0);var c=e.headers||{};for(var d in c)o.setRequestHeader(d,c[d]);var w=e.data;if(t(w,"object"))w=r(w);o.send(w)}}}();var a=function(){var e=function(e){try{return new Function("return "+e)()}catch(n){return null}};return function(n){if("string"!=typeof n)return n;try{if(window.JSON&&JSON.parse)return JSON.parse(n)}catch(t){return n}return e(n)}}();window.addMsgListener(function(e){o(a(e.data))})}}();</script>
</head> <body></body> </html>

================================================
FILE: res/nej_proxy_upload.html
================================================
<html><body>
<!-- 将上传结果序列化为JSON串后填入result中即可 -->
<script>
    window.result = '{"url":"http://192.168.146.84:1122/xhr/3.jpg"}';
</script>
<script>
    var _result = 'NEJ-UPLOAD-RESULT:{"key":"'+window.name+'","result":"'+encodeURIComponent(result)+'"}';
    if (window.postMessage){
        parent.postMessage(_result,'*');
    }else{
        _result = '"'+_result.replace(/"/g,'\\"')+'"';
        parent.name = escape('MSG|data='+encodeURIComponent(_result));
    }
</script>
</body></html>

================================================
FILE: res/src/nej_proxy_frame.html
================================================
<html>
  <head>
    <title>NEJ AJAX PROXY</title>
    <meta charset="utf-8"/>
    <script>window.whitelist = [/.*/i];</script>
    <script>
      // for post message and onmessage event
      (function(){
          var _listeners = [];
          window.addMsgListener = function(_hanlder){
              _listeners.push(_hanlder);
          };
          var _onMessage = function(_event){
              for(var i=0,l=_listeners.length;i<l;i++)
                  try{_listeners[i].call(null,_event);}catch(e){}
          };
          if (!!window.postMessage){
              window.doPostMessage = function(_window,_options){
                  _options = _options||{};
                  _window.postMessage(''+_options.data,_options.origin||'*');
              };
              if (!!window.addEventListener){
                  window.addEventListener('message',_onMessage,!1);
              }else{
                  window.attachEvent('onmessage',_onMessage);
              }
              return;
          }
          var _key = 'MSG|',
              _queue = [];
          /*
           * 检测window.name变化情况
           * @return {Void}
           */
          var _doCheckWindowName = (function(){
              var _doStringObject = function(_string){
                  var _result = {},
                      _arr = _string.split('|');
                  for(var i=0,l=_arr.length,_brr;i<l;i++){
                      _brr = _arr[i].split('=');
                      _result[decodeURIComponent(_brr.shift())]
                            = decodeURIComponent(_brr.join('='));
                  }
                  return _result;
              };
              return function(){
                  // check name
                  var _name = unescape(window.name||'');
                  if (!_name||_name.indexOf(_key)!=0) return;
                  // clear name
                  window.name = '';
                  _name = _name.replace(_key,'');
                  // onmessage trigger
                  var _options = _doStringObject(_name),
                      _origin = (_options.origin||'').toLowerCase();
                  // check origin
                  if (!!_origin&&_origin!='*'&&
                      location.href.toLowerCase().indexOf(_origin)!=0)
                      return;
                  _onMessage({
                      data:_options.data,
                      origin:document.referrer
                  });
              };
          })();
          /*
           * 检查待设置消息
           * @return {Void}
           */
          var _doCheckNameQueue = (function(){
              var _check;
              var _hasItem = function(_list,_item){
                  for(var i=0,l=_list.length;i<l;i++)
                      if (_list[i]==_item)
                          return !0;
                  return !1;
              };
              return function(){
                  if (!_queue.length) return;
                  _check = [];
                  for(var i=_queue.length-1,_map;i>=0;i--){
                      _map = _queue[i];
                      if (!_hasItem(_check,_map.w)){
                          _check.push(_map.w);
                          _queue.splice(i,1);
                          _map.w.name = _map.d;
                      }
                  }
                  _check = null;
              };
          })();
          /**
           * 发送消息
           * @param  {Window} 窗体对象
           * @param  {Object} 配置信息
           * @config {String} data   消息内容,只支持字符串
           * @config {String} origin 目标Origin,只有指定的页面可以收到消息,默认所有页面可收到消息,如http://a.b.com
           * @return {Void}
           */
          window.doPostMessage = (function(){
              // object to query
              var _doQueryObject = function(_object){
                  var _arr = [];
                  for(var x in _object)
                      _arr.push(encodeURIComponent(x)+'='+
                                encodeURIComponent(_object[x]));
                  return _arr.join('|');
              };
              // serialize send data
              var _doSerialize = function(_data){
                  var _result = {};
                  _data = _data||{};
                  _result.origin = _data.origin||'*';
                  _result.ref  = location.href;
                  _result.data = ''+_data.data;
                  return _key+_doQueryObject(_result);
              };
              return function(_window,_options){
                  _queue.unshift({w:_window,d:escape(_doSerialize(_options))});
              };
          })();
          // init checker
          window.setInterval(_doCheckNameQueue,100);
          window.setInterval(_doCheckWindowName,50);
      })();
      // for ajax request
      (function(){
          // access control
          var _hasPermission = function(){
              var _ref = document.referrer||'';
              if (!_ref||!whitelist.length) 
                  return !1;
              for(var i=0,l=whitelist.length,_item;i<l;i++){
                  _item = whitelist[i];
                  if (!!_item.test&&
                      _item.test(_ref)||_ref==_item)
                      return !0;
              }
              return !1;
          };
          if (!_hasPermission()) return;
          // ajax request implementation
          var f = function(){};
          var _isTypeOf = function(_data,_type){
              try{
                  _type = _type.toLowerCase();
                  if (_data===null) return _type=='null';
                  if (_data===undefined) return _type=='undefined';
                  return Object.prototype.toString.call(_data).toLowerCase()=='[object '+_type+']';
              }catch(e){
                  return !1;
              }
          };
          var _obj2str = function (obj) {
              var ret = [];
              for(var x in obj){
                  if (!obj.hasOwnProperty(x)) continue;
                  ret.push(encodeURIComponent(x)+'='+encodeURIComponent(obj[x]));
              }
              return ret.join('&');
          };
          /*
           * 发送请求
           * @param  {Object} 请求信息
           * @config {String} key     请求标识,回调时传回
           * @config {String} url     请求地址
           * @config {String} method  请求方式,默认GET
           * @config {Number} timeout 超时时间,默认不做超时检测
           * @config {Object} headers 头信息
           * @config {String} data    发送数据,字符串格式
           * @return {Void}
           */
          var _doSendRequest = (function(){
              var _reg0  = /"/gi,
                  _msxml = ['Msxml2.XMLHTTP.6.0'
                           ,'Msxml2.XMLHTTP.3.0'];
              var _getXHR = function(){
                  if (window.XMLHttpRequest)
                      return new XMLHttpRequest();
                  try{
                      for(var i=0,l=_msxml.length;i<l;i++)
                          return new ActiveXObject(_msxml[i]);
                  }catch(ex){
                      return null;
                  }
              };
              var _doCallback = function(_options){
                  var _data = 'NEJ-AJAX-DATA:{\
                          "key":"'+_options.key+'",\
                          "status":"'+_options.status+'",\
                          "result":"'+encodeURIComponent(_options.result)+'"\
                      }';
                  if (!window.postMessage)
                      _data = '"'+_data.replace(_reg0,'\\"')+'"';
                  window.doPostMessage(parent,{data:_data});
              };
              return function(_options){
                  if (!_options||!_options.url) return;
                  var _xhr = _getXHR(),
                      _key = _options.key;
                  // set timeout
                  var _timer,
                      _timeout = parseInt(_options.timeout)||0;
                  if (!!_timeout){
                      _timer = window.setTimeout(function(){
                          _xhr.onreadystatechange = f;
                          _xhr.abort();
                          _doCallback({key:_key,status:-1});
                      },_timeout);
                  }
                  // set callback
                  _xhr.onreadystatechange = function(){
                      if (_xhr.readyState!=4) return;
                      window.clearTimeout(_timer);
                      _doCallback({
                          key:_key,
                          status:_xhr.status,
                          result:_xhr.responseText||''
                      });
                  };
                  _xhr.open(_options.method||'GET',_options.url,!0);
                  // set headers
                  var _headers = _options.headers||{};
                  for(var x in _headers)
                      _xhr.setRequestHeader(x,_headers[x]);
                  // fix object to query string
                  var dat = _options.data;
                  if (_isTypeOf(dat,'object')){
                      dat = _obj2str(dat);
                  }
                  _xhr.send(dat);
              };
          })();
          /*
           * 解析JSON
           * @param  {String}   JSON串
           * @return {Variable} JSON值
           */
          var _doParseJSON = (function(){
              var _doEvalScript = function(_content){
                  try{
                      return (new Function('return '+_content))();
                  }catch(e){
                      return null;
                  }
              };
              return function(_content){
                  if (typeof(_content)!='string')
                      return _content;
                  try{
                      if (!!window.JSON&&!!JSON.parse)
                          return JSON.parse(_content);
                  }catch(e){
                      return _content;
                  }
                  return _doEvalScript(_content);
              };
          })();
          // listen ajax request
          window.addMsgListener(function(_event){
              _doSendRequest(_doParseJSON(_event.data));
          });
      })();
    </script>
  </head>
  <body></body>
</html>

================================================
FILE: res/src/nej_proxy_upload.html
================================================
<html><body>
<!-- 将上传结果序列化为JSON串后填入result中即可 -->
<script>
    window.result = '{"url":"http://192.168.146.84:1122/xhr/3.jpg"}';
</script>
<script>
    var _result = 'NEJ-UPLOAD-RESULT:{"key":"'+window.name+'","result":"'+encodeURIComponent(result)+'"}';
    if (window.postMessage){
        parent.postMessage(_result,'*');
    }else{
        _result = '"'+_result.replace(/"/g,'\\"')+'"';
        parent.name = escape('MSG|data='+encodeURIComponent(_result));
    }
</script>
</body></html>

================================================
FILE: src/base/.dep
================================================
基础库依赖关系

global    <-    klass
          <-    platform
          <-    config      <-    constant
          <-    util        <-    element    <->    event

================================================
FILE: src/base/chain.js
================================================
/*
 * ------------------------------------------
 * 可链式接口实现代理文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
/** @namespace CHAINABLE */
/** @module base/chain */
NEJ.define([
    'base/util'
],function(_u,_p){
    var _cache = {};
    /**
     * 添加可链式调用的接口
     *
     * 添加可链式接口
     * ```javascript
     * NEJ.define([
     *     'base/chain'
     * ],function(_l){
     *     var _map = {};
     *
     *      _map._$api1 = function(){
     *          // TODO
     *      }
     *      
     *      _map._$api2 = function(){
     *          // TODO
     *      }
     * 
     *     _l._$merge(_map);
     * });
     * ```
     *
     * 使用链式调用接口
     * ```javascript
     * NEJ.define([
     *     '/path/to/api.js',
     *     'util/chain/chainable'
     * ],function(_x,$){
     *     // 使用链式调用api
     *     $('body > p')._$api1()._$api2();
     * });
     * ```
     * 
     * @method module:base/chain._$merge
     * @param  {Object} arg0 - 接口集合
     * @return {Void}
     */
    _p._$merge = function(_map){
        _u._$merge(_cache,_map);
    };
    /**
     * 导出链式接口列表
     * 
     * @method module:base/chain._$dump
     * @return {Object} 链式接口列表
     */
    _p._$dump = function(){
        return _cache;
    };
    /**
     * 清除链式列表
     * 
     * @method module:base/chain._$clear
     * @return {Void}
     */
    _p._$clear = function(){
        _cache = {};
    };
    
    return _p;
});


================================================
FILE: src/base/config.js
================================================
/*
 * ------------------------------------------
 * 平台配置信息
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
/** @module base/config */
NEJ.define([
    './global.js',
    '{platform}config.js'
],function(NEJ,_h,_p,_o,_f,_r){
    /**
     * 取Frame跨域Ajax代理文件,通过NEJ_CONF的p_frame配置给定域名的代理文件地址
     *
     * @method module:base/config._$getFrameProxy
     * @see    module:base/config._$get
     * @param  {String} arg0 - 请求地址或者域名
     * @return {String}        代理文件地址
     */
    _p._$getFrameProxy = function(_url){
        var _host = _h.__url2host(_url);
        return _p._$get('frames')[_host]||
              (_host+'/res/nej_proxy_frame.html');
    };
    /**
     * 取Flash跨域Ajax配置文件,通过NEJ_CONF的p_flash配置给定域名的代理文件地址
     *
     * @method module:base/config._$getFlashProxy
     * @see    module:base/config._$get
     * @param  {String} arg0 - 请求地址或者域名
     * @return {String}        代理文件地址
     */
    _p._$getFlashProxy = function(_url){
        return _p._$get('flashs')[_h.__url2host(_url)];
    };
    /**
     * 获取NEJ配置信息,通过NEJ_CONF配置相关信息
     *
     * ```javascript
     *  window.NEJ_CONF = {
     *      // resource root
     *      // defalut value -> '/res/'
     *      root : '/nej/'
     *      // blank image for ie6-ie7
     *      // default value -> $root+'nej_blank.gif'
     *      blank : '/res/nej_blank.gif'
     *      // localstorage flash
     *      // default value -> $root+'nej_storage.swf'
     *      storage : '/res/nej_storage.swf'
     *      // audio player flash
     *      // default value -> $root+'nej_player_audio.swf'
     *      audio : '/res/nej_player_audio.swf'
     *      // video player flash
     *      // default value -> $root+'nej_player_video.swf'
     *      video : '/res/nej_player_video.swf'
     *      // clipboard flash
     *      // default value -> $root+'nej_clipboard.swf'
     *      clipboard : '/res/nej_clipboard.swf'
     *      // https request proxy
     *      // default value -> $root+'nej_proxy_flash.swf'
     *      ajax : '/res/nej_proxy_flash.swf'
     *      // portrait root
     *      // default value -> $root+'portrait/'
     *      portrait : '/res/portrait/'
     *      // cross domain xhr request for ie6-ie9
     *      // if path not start with http[s]://
     *      // will use /res/nej_proxy_frame.html as default
     *      p_frame:['http://c.d.com/html/nej_proxy_frame.html']
     *      // flash crossdomain.xml file path
     *      // default value -> http://a.b.com/crossdomain.xml
     *      p_flash:['http://a.b.com/proxy/crossdomain.xml']
     *      // CSRF cookie name and parameter name
     *      // set p_csrf:true to use URS config {cookie:'AntiCSRF',param:'AntiCSRF'}
     *      // default value -> {cookie:'',param:''}
     *      p_csrf:{cookie:'AntiCSRF',param:'AntiCSRF'}
     *  };
     * ```
     *
     * 配置标识支持
     *
     * | 标识                          | 说明 |
     * | :--              | :-- |
     * | portrait         | 表情根路径 |
     * | blank.png        | 空白图片文件地址 |
     * | ajax.swf         | Ajax代理Flash文件地址 |
     * | chart.swf        | 图标Flash文件地址 |
     * | audio.swf        | 实现Audio功能的Flash文件地址 |
     * | video.swf        | 实现Video功能的Flash文件地址 |
     * | clipboard.swf    | 实现剪切板功能的Flash文件地址 |
     * | upload.image.swf | 实现图片上传功能的Flash文件地址 |
     * | storage.swf      | 实现本地存储功能的Flash文件地址 |
     *
     * @method module:base/config._$get
     * @param  {String}   arg0 - 配置标识
     * @return {Variable}        配置信息
     */
    _p._$get = function(_key){
        return _h.__get(_key);
    };

    if (CMPT){
        NEJ.copy(NEJ.P('nej.c'),_p);
    }

    return _p;
});


================================================
FILE: src/base/constant.js
================================================
/*
 * ------------------------------------------
 * 常量定义文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
/** @module base/constant */
NEJ.define([
    './global.js',
    './config.js'
],function(NEJ,_c,_p,_o,_f,_r){
    var _seed = +new Date;
    /**
     * 找不到指定内容的错误码
     *
     * @const {Number} module:base/constant._$CODE_NOTFUND
     */
    _p._$CODE_NOTFUND = 10000-_seed;
    /**
     * 需要指定的参数未指定的错误码
     *
     * @const {Number} module:base/constant._$CODE_NOTASGN
     */
    _p._$CODE_NOTASGN = 10001-_seed;
    /**
     * 不支持操作的错误码
     *
     * @const {Number} module:base/constant._$CODE_NOTSPOT
     */
    _p._$CODE_NOTSPOT = 10002-_seed;
    /**
     * 操作超时的错误码
     *
     * @const {Number} module:base/constant._$CODE_TIMEOUT
     */
    _p._$CODE_TIMEOUT = 10003-_seed;
    /**
     * 字符串作为脚本执行异常的错误码
     *
     * @const {Number} module:base/constant._$CODE_ERREVAL
     */
    _p._$CODE_ERREVAL = 10004-_seed;
    /**
     * 回调执行异常的错误码
     *
     * @const {Number} module:base/constant._$CODE_ERRCABK
     */
    _p._$CODE_ERRCABK = 10005-_seed;
    /**
     * 服务器返回异常的错误码
     *
     * @const {Number} module:base/constant._$CODE_ERRSERV
     */
    _p._$CODE_ERRSERV = 10006-_seed;
    /**
     * 异常终止的错误码
     *
     * @const {Number} module:base/constant._$CODE_ERRABRT
     */
    _p._$CODE_ERRABRT = 10007-_seed;
    /**
     * 请求头content-type统一名称
     *
     * @const {Number} module:base/constant._$HEAD_CT
     */
    _p._$HEAD_CT      = 'Content-Type';
    /**
     * 文本请求头content-type值
     *
     * @const {String} module:base/constant._$HEAD_CT_PLAN
     */
    _p._$HEAD_CT_PLAN = 'text/plain';
    /**
     * 文件请求头content-type值
     *
     * @const {String} module:base/constant._$HEAD_CT_FILE
     */
    _p._$HEAD_CT_FILE = 'multipart/form-data';
    /**
     * 表单请求头content-type值
     *
     * @const {String} module:base/constant._$HEAD_CT_FORM
     */
    _p._$HEAD_CT_FORM = 'application/x-www-form-urlencoded';
    /**
     * 空图片BASE64编码地址,低版本浏览器使用图片地址
     *
     * @const {String} module:base/constant._$BLANK_IMAGE
     */
    _p._$BLANK_IMAGE  = _c._$get('blank.png')||'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';

    if (CMPT){
        NEJ.copy(NEJ.P('nej.g'),_p);
    }

    return _p;
});


================================================
FILE: src/base/demo/define/a.css
================================================
.a{color:#f00;}
.b{color:#0000FF;}

================================================
FILE: src/base/demo/define/a.js
================================================
console.log('aaaaa');


================================================
FILE: src/base/demo/define/a.json
================================================
{
    "a":"aaaaaaaaa",
    "b":"bbbbbbbbbb"
}


================================================
FILE: src/base/demo/define/define.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>define demo</title>
    <meta charset="utf-8" />
    <script>
      function log(m){
          var p = document.createElement('p');
          p.innerHTML = m;
          document.body.appendChild(p);
      }
    </script>
      <script src="../../../define.js?pro=/base/demo/define/&abc=/base/demo/"></script>
  </head>
  <body>

    <script>
      define([
          'pro/a',
          'base/element',
          'json!abc/define/a.json',
          'res!./a/b/c.swf',
          'res!/a/c.swf'
      ],function(a,e,json,swf,xx){
          log('a:'+json.a);
          log(swf);
          log(xx);
      });
    </script>
  </body>
</html>

================================================
FILE: src/base/demo/define/style.html
================================================
<!DOCTYPE html>
<html>
    <head>
        <title>define demo</title>
        <meta charset="utf-8" />
        <script>
            function log(m){
                var p = document.createElement('p');
                p.innerHTML = m;
                document.body.appendChild(p);
            }
        </script>
        <script src="../../../define.js?pro=/base/demo/define/&abc=/base/demo/"></script>
    </head>
    <body>
        <p class="a">aaaaaaaaaaaaaaaa</p>
        <p class="b">bbbbbbbbbbbbbbbbbbbbbbbbbb</p>
        <script>
            define([
                'css!./a.css'
            ],function(a){
                log('a:'+a);
            });
        </script>
    </body>
</html>

================================================
FILE: src/base/demo/element.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>element demo</title>
    <meta charset="utf-8" />
    <script>
      function log(m){
          var p = document.createElement('p');
          p.innerHTML = m;
          document.body.appendChild(p);
      }
    </script>
  </head>
  <body>
    
    <script src="../../define.js"></script>
    <script>
      NEJ.define([
          'base/element'
      ],function(_e){
          // create element and get by id
          var _div = _e._$create('div');
          _div.innerHTML = '<p id="a">aaaa</p>';
          log(_e._$get('a'));
          // get element after set element id in memory
          var _div = document.createElement('div');
          var _id = _e._$id(_div);
          log(_e._$get(_id));
          // parse element
          log(_e._$html2node('<TR><td>col1</td><td>col2</td></TR>'));
      });
    </script>
  </body>
</html>

================================================
FILE: src/base/demo/event.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>element demo</title>
    <meta charset="utf-8" />
    <script>
      function log(m){
          var p = document.createElement('p');
          p.innerHTML = m;
          document.body.appendChild(p);
      }
    </script>
  </head>
  <body>
    <div>
        <p id="abc">aaaaaaaaaaaaa</p>
    </div>
    <input type="button" value="aaaa" onclick="document.title=+new Date;"/>
    <textarea id="abcd"></textarea>
    <input type="button" value="add" id="btn-add"/>
    <input type="button" value="del" id="btn-del"/>
    <script src="../../define.js"></script>
    <script>
      NEJ.define([
          'base/event',
          'base/element'
      ],function(_v,_e){
//          _v._$addEvent(
//              document,'click',function(event){
//                  log('document '+(this==document));
//              }
//          );
//          _v._$addEvent(
//              document.body,'click',function(event){
//                  log('body '+(this==document.body));
//              }
//          );
//          _v._$addEvent(
//              'abc','click',function(event){
//                  log('abc '+(this.id=='abc'));
//              }
//          );
//          _v._$addEvent(
//              document,'propertychange',function(event){
//                  console.log(event.propertyName+'->'+document.title);
//              }
//          );
          var tx = _e._$get('abcd');
          var handler = function(){
              console.log('input ->'+tx.value);
          }
          _v._$addEvent('btn-add','click',function(){
              document.body.appendChild(tx);
              _v._$addEvent(tx,'input',handler);
          });
          _v._$addEvent('btn-del','click',function(){
              _v._$delEvent(tx,'input',handler);
              _e._$removeByEC(tx);
              tx.value = '';
          });
      });
    </script>
  </body>
</html>

================================================
FILE: src/base/demo/klass.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>klass demo</title>
    <meta charset="utf-8" />
    <script>
      function log(m){
          var p = document.createElement('p');
          p.innerHTML = m;
          document.body.appendChild(p);
      }
    </script>
  </head>
  <body>
    <script src="../../define.js"></script>
    <script>
      NEJ.define([
          'base/klass',
          'util/event'
      ],function(_k,_t,p){
          p.A = _k._$klass();
          var pro = p.A._$extend(_t._$$EventTarget);
          pro.a = function(){
              log('a in A');
              this.b();
          };
          pro.b = function(){
              log('b in A');
          };

          p.B = _k._$klass();
          var pro = p.B._$extend(p.A);
          pro.a = function(){
              this.__super();
              log('a in B');
          };
          pro.b = function(){
              this.__super();
              this.c();
              log('b in B');
          };
          pro.c = function(){
              log('c in B');
          };

          p.C = _k._$klass();
          var pro = p.C._$extend(p.B);
          pro.a = function(){
              this.__super();
              log('a in C');
          };
          pro.b = function(){
              this.__super();
              log('b in C');
          };
          pro.c = function(){
              log('c in C');
              this.__super();
          };


          var c = p.C._$allocate();
          c.a();
      });
    </script>
  </body>
</html>

================================================
FILE: src/base/demo/test.html
================================================
<!DOCTYPE html>
<html>
<head>
    <title>klass demo</title>
    <meta charset="utf-8" />
    <script>
        function log(m){
            var p = document.createElement('p');
            p.innerHTML = m;
            document.body.appendChild(p);
        }
    </script>
</head>
<body>
<script src="../../define.js"></script>
<script>
    NEJ.define([
        'base/klass',
        'util/event'
    ],function(_k,_t,p){
        var t = +new Date;
        p.A0 = _k._$klass();
        pro = p.A0.prototype;
        for(var i=0;i<1000;i++){
            pro['a'+i] = function(){
                console.log('aaaaaaa');
            };
        }
        console.log(+new Date-t);
        var t = +new Date;
        for(var i=0;i<100;i++){
            var name = 'A'+(i+1);
            p[name] = _k._$klass();
            var pro = p[name]._$extend(p['A'+i]);
            for(var j=0;j<1000;j++){
                pro['a'+j] = (function(last){
                    return function(){
                        if (last>=0){
                            this['a'+last]();
                        }
                        this.__super();
                        console.log('a');
                    };
                })(j-1);
            }
        }
        console.log(+new Date-t);
        console.log(p);

        var t = +new Date;
        var a = new p.A100();
        a.a999();
        console.log(+new Date-t);
    });
</script>
</body>
</html>

================================================
FILE: src/base/demo/util.html
================================================
<!DOCTYPE html>
<html>
  <head>
    <title>util demo</title>
    <meta charset="utf-8" />
    <script>
      function log(m){
          var p = document.createElement('p');
          p.innerHTML = m;
          document.body.appendChild(p);
      }
    </script>
  </head>
  <body>
    <form id="abc">
        <input type="text" name="a"/>
        <select name="b"><option>aaa</option></select>
    </form>
    <script src="../../define.js"></script>
    <script>
      NEJ.define([
          'base/element',
          'base/util'
      ],function(_e,_u){
          _u._$forEach(
              _e._$get('abc').elements,function(e){
                  log(e.name);
              }
          );

          log(_u._$string2object('a=aaa&b=bbbbb%gh','&'));
          log(_u._$object2string({a:1111,b:2222,c:'%123455555'},'&'));
      });
    </script>
  </body>
</html>

================================================
FILE: src/base/element.js
================================================
/*
 * ------------------------------------------
 * 节点接口实现文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
/** @module base/element */
NEJ.define([
    './global.js',
    './constant.js',
    './util.js',
    './event.js',
    './chain.js',
    '{platform}element.js'
],function(NEJ,_g,_u,_v,_x,_h,_p,_o,_f,_r){
    // variables
    var _y = {},     // chainable methods
        _cspol,      // css text pool
        _empol = {}, // elements without id property, eg. document,window
        _dirty = {}, // temporary element with id
        _fragment = document.createDocumentFragment(); // node in memory
    // init
    if (!document.head){
         document.head = document.getElementsByTagName('head')[0]||document.body;
    }
    // only for test
    _p.dump = function(){
        return {
            pool:_empol,
            dirty:_dirty,
            fragment:_fragment
        };
    };
    /**
     * 为节点设置一个唯一的标识
     *
     * 结构举例
     * ```html
     *    <div id="abc">aaaaa</div>
     * ```
     *
     * 脚本举例
     * ```javascript
     *   NEJ.define([
     *       'base/element'
     *   ],function(_e){
     *       // 如果有id,返回原来的id,否则返回auto-id-12345678(8位随机字符串)
     *       var _id = _e._$id(_node||"abc");
     *   });
     * ```
     *
     * @method module:base/element._$id
     * @param  {String|Node} arg0 - 节点标识或者对象
     * @return {String}             节点标识
     */
    /**
     * @method CHAINABLE._$id
     * @see module:base/element._$id
     */
    _p._$id =
    _y._$id = function(_element){
        _element = _p._$get(_element);
        if (!_element) return;
        var _id = !!_element.id ? _element.id
                : 'auto-id-'+_u._$uniqueID();
        if (!('id' in _element)){
            _empol[_id] = _element;
        }
        _element.id = _id;
        // check if element can be getted
        if (!_p._$get(_id)){
            _dirty[_id] = _element;
        }
        return _id;
    };
    /**
     * 根据标识取节点对象,包括在内存中的节点
     *
     * 结构举例
     * ```html
     *   <div id="abc">123</div>
     * ```
     *
     * 脚本举例
     * ```javascript
     *   NEJ.define([
     *       'base/element'
     *   ],function(_e){
     *       // 先根据id从内存中取,再从页面取
     *       var _node = _e._$get("abc");
     *   });
     * ```
 
Download .txt
gitextract_zy1dvyqz/

├── .gitignore
├── CHANGELOG
├── LICENSE
├── README.md
├── bower.json
├── doc/
│   ├── AJAX.md
│   ├── CACHE.md
│   ├── DEPENDENCY.md
│   ├── DISPATCHER.md
│   ├── FAQ.md
│   ├── MESSAGE.md
│   ├── PLATFORM.md
│   ├── TEMPLATE.md
│   ├── WIDGET.md
│   └── guide/
│       ├── Build.Scalable.Web.System/
│       │   ├── Module.md
│       │   └── Platform.md
│       └── Build.Scalable.Web.System.md
├── package.json
├── res/
│   ├── cors/
│   │   ├── cross-origin-0.0.2-sources.jar
│   │   └── cross-origin-0.0.2.jar
│   ├── crossdomain.xml
│   ├── nej_clipboard.swf
│   ├── nej_player_audio.swf
│   ├── nej_proxy_flash.swf
│   ├── nej_proxy_frame.html
│   ├── nej_proxy_upload.html
│   ├── nej_storage.swf
│   ├── nej_upload_image.swf
│   └── src/
│       ├── nej_proxy_frame.html
│       └── nej_proxy_upload.html
└── src/
    ├── base/
    │   ├── .dep
    │   ├── chain.js
    │   ├── config.js
    │   ├── constant.js
    │   ├── demo/
    │   │   ├── define/
    │   │   │   ├── a.css
    │   │   │   ├── a.js
    │   │   │   ├── a.json
    │   │   │   ├── define.html
    │   │   │   └── style.html
    │   │   ├── element.html
    │   │   ├── event.html
    │   │   ├── klass.html
    │   │   ├── test.html
    │   │   └── util.html
    │   ├── element.js
    │   ├── event.js
    │   ├── global.js
    │   ├── klass.js
    │   ├── platform/
    │   │   ├── config.js
    │   │   ├── config.patch.js
    │   │   ├── element.js
    │   │   ├── element.patch.js
    │   │   ├── event.js
    │   │   ├── event.patch.js
    │   │   ├── util.js
    │   │   └── util.patch.js
    │   ├── platform.js
    │   ├── test/
    │   │   ├── config.test.html
    │   │   ├── config.test.js
    │   │   ├── constant.test.html
    │   │   ├── constant.test.js
    │   │   ├── element.test.html
    │   │   ├── element.test.js
    │   │   ├── event.test.html
    │   │   ├── event.test.js
    │   │   ├── global.test.html
    │   │   ├── global.test.js
    │   │   ├── klass.test.html
    │   │   ├── klass.test.js
    │   │   ├── platform.test.html
    │   │   ├── platform.test.js
    │   │   ├── util.test.html
    │   │   └── util.test.js
    │   └── util.js
    ├── define.js
    ├── ui/
    │   ├── audio/
    │   │   ├── audio.css
    │   │   ├── audio.html
    │   │   ├── audio.js
    │   │   ├── mp3.css
    │   │   ├── mp3.html
    │   │   ├── mp3.js
    │   │   └── test/
    │   │       ├── audio.test.html
    │   │       ├── audio.test.js
    │   │       ├── mp3.test.html
    │   │       └── mp3.test.js
    │   ├── base.js
    │   ├── carousel/
    │   │   ├── carousel.js
    │   │   ├── carousel.list.js
    │   │   ├── carousel.x.js
    │   │   ├── carousel.y.js
    │   │   ├── indicator.js
    │   │   ├── list.js
    │   │   ├── test/
    │   │   │   ├── carousel.test.html
    │   │   │   └── carousel.test.js
    │   │   ├── x.js
    │   │   └── y.js
    │   ├── carousel.list/
    │   │   └── carousel.list.js
    │   ├── colorpick/
    │   │   ├── colorpanel.css
    │   │   ├── colorpanel.html
    │   │   ├── colorpanel.js
    │   │   ├── colorpick.complex.js
    │   │   ├── colorpick.css
    │   │   ├── colorpick.html
    │   │   ├── colorpick.js
    │   │   ├── colorpick.simple.js
    │   │   ├── complex.html
    │   │   ├── complex.js
    │   │   ├── demo/
    │   │   │   ├── colorpanel.html
    │   │   │   ├── colorpick.html
    │   │   │   ├── complex.html
    │   │   │   └── simple.html
    │   │   ├── simple.css
    │   │   ├── simple.html
    │   │   ├── simple.js
    │   │   └── test/
    │   │       ├── colorpanel.test.html
    │   │       └── colorpanel.test.js
    │   ├── datepick/
    │   │   ├── datepick.css
    │   │   ├── datepick.html
    │   │   ├── datepick.js
    │   │   ├── demo/
    │   │   │   └── datepick.html
    │   │   └── test/
    │   │       ├── datepick.test.html
    │   │       └── datepick.test.js
    │   ├── editor/
    │   │   ├── command/
    │   │   │   ├── color.complex.js
    │   │   │   ├── color.css
    │   │   │   ├── color.js
    │   │   │   ├── color.simple.js
    │   │   │   ├── complex.css
    │   │   │   ├── complex.js
    │   │   │   ├── font.css
    │   │   │   ├── font.html
    │   │   │   ├── font.js
    │   │   │   ├── fontname.js
    │   │   │   ├── fontsize.js
    │   │   │   ├── link.css
    │   │   │   ├── link.html
    │   │   │   ├── link.js
    │   │   │   ├── simple.css
    │   │   │   ├── simple.js
    │   │   │   ├── uploadimage.css
    │   │   │   ├── uploadimage.html
    │   │   │   └── uploadimage.js
    │   │   ├── custom.js
    │   │   ├── demo/
    │   │   │   └── custom.html
    │   │   ├── editor.css
    │   │   ├── editor.html
    │   │   ├── editor.js
    │   │   └── test/
    │   │       ├── custom.test.html
    │   │       └── custom.test.js
    │   ├── item/
    │   │   ├── item.js
    │   │   └── list.js
    │   ├── layer/
    │   │   ├── card.css
    │   │   ├── card.js
    │   │   ├── card.wrapper.js
    │   │   ├── demo/
    │   │   │   ├── card.html
    │   │   │   └── window.html
    │   │   ├── layer.js
    │   │   ├── layer.wrapper.js
    │   │   ├── test/
    │   │   │   ├── card.test.html
    │   │   │   ├── card.test.js
    │   │   │   ├── layer.test.html
    │   │   │   ├── layer.test.js
    │   │   │   ├── mylayer.js
    │   │   │   ├── mylayercard.js
    │   │   │   ├── mylayerwrapper.js
    │   │   │   ├── mywindow.js
    │   │   │   ├── window.test.html
    │   │   │   ├── window.test.js
    │   │   │   ├── window.wrapper.test.html
    │   │   │   └── window.wrapper.test.js
    │   │   ├── window.css
    │   │   ├── window.html
    │   │   ├── window.js
    │   │   ├── window.wrapper.js
    │   │   └── wrapper/
    │   │       ├── card.js
    │   │       ├── layer.js
    │   │       └── window.js
    │   ├── lightbox/
    │   │   ├── demo/
    │   │   │   └── lightbox.html
    │   │   ├── lightbox.css
    │   │   ├── lightbox.html
    │   │   └── lightbox.js
    │   ├── mask/
    │   │   ├── mask.css
    │   │   ├── mask.js
    │   │   └── test/
    │   │       ├── mask.test.html
    │   │       └── mask.test.js
    │   ├── pager/
    │   │   ├── base.css
    │   │   ├── base.js
    │   │   ├── demo/
    │   │   │   └── pager.html
    │   │   ├── pager.base.js
    │   │   ├── pager.js
    │   │   ├── pager.simple.js
    │   │   ├── simple.js
    │   │   └── test/
    │   │       ├── pager.test.html
    │   │       └── pager.test.js
    │   ├── portrait/
    │   │   ├── complex.css
    │   │   ├── complex.html
    │   │   ├── complex.js
    │   │   ├── demo/
    │   │   │   ├── complex.html
    │   │   │   └── simple.html
    │   │   ├── portrait.complex.js
    │   │   ├── portrait.css
    │   │   ├── portrait.html
    │   │   ├── portrait.js
    │   │   ├── portrait.simple.js
    │   │   └── simple.js
    │   ├── range/
    │   │   ├── range.css
    │   │   └── range.js
    │   ├── resizer/
    │   │   ├── demo/
    │   │   │   └── resizer.html
    │   │   ├── resizer.css
    │   │   ├── resizer.html
    │   │   └── resizer.js
    │   ├── scroller/
    │   │   ├── demo/
    │   │   │   └── y.html
    │   │   ├── list.css
    │   │   ├── list.js
    │   │   ├── scroller.js
    │   │   ├── scroller.x.js
    │   │   ├── scroller.y.js
    │   │   ├── test/
    │   │   │   ├── scroller.y.test.html
    │   │   │   └── scroller.y.test.js
    │   │   ├── x.css
    │   │   ├── x.js
    │   │   ├── y.css
    │   │   └── y.js
    │   ├── scroller.list/
    │   │   ├── scroller.list.js
    │   │   └── test/
    │   │       ├── scroller.list.test.html
    │   │       └── scroller.list.test.js
    │   ├── suggest/
    │   │   ├── demo/
    │   │   │   └── suggest.html
    │   │   ├── suggest.css
    │   │   ├── suggest.html
    │   │   ├── suggest.js
    │   │   └── test/
    │   │       ├── suggest.test.html
    │   │       └── suggest.test.js
    │   └── timepick/
    │       ├── demo/
    │       │   └── timepick.html
    │       ├── test/
    │       │   ├── timepick.test.html
    │       │   └── timepick.test.js
    │       ├── timepick.css
    │       ├── timepick.html
    │       └── timepick.js
    └── util/
        ├── ajax/
        │   ├── demo/
        │   │   ├── a.html
        │   │   ├── a.js
        │   │   ├── b.html
        │   │   ├── b.json
        │   │   ├── rest.html
        │   │   ├── tag.html
        │   │   ├── upload.html
        │   │   └── xdr.html
        │   ├── dwr.js
        │   ├── jsonp.js
        │   ├── loader/
        │   │   ├── html.js
        │   │   ├── loader.js
        │   │   ├── platform/
        │   │   │   ├── html.js
        │   │   │   └── html.patch.js
        │   │   ├── script.js
        │   │   ├── style.js
        │   │   ├── test/
        │   │   │   ├── loader.test.html
        │   │   │   └── loader.test.js
        │   │   └── text.js
        │   ├── message.js
        │   ├── platform/
        │   │   ├── message.js
        │   │   ├── message.patch.js
        │   │   ├── xdr.js
        │   │   └── xdr.patch.js
        │   ├── proxy/
        │   │   ├── flash.js
        │   │   ├── frame.js
        │   │   ├── platform/
        │   │   │   ├── xhr.js
        │   │   │   └── xhr.patch.js
        │   │   ├── proxy.js
        │   │   ├── upload.js
        │   │   └── xhr.js
        │   ├── rest.js
        │   ├── tag.js
        │   ├── test/
        │   │   ├── a.html
        │   │   ├── a.js
        │   │   ├── b.html
        │   │   ├── b.js
        │   │   ├── message.test.html
        │   │   ├── message.test.js
        │   │   ├── tag.test.html
        │   │   ├── tag.test.js
        │   │   ├── x.css
        │   │   ├── xdr.test.html
        │   │   ├── xdr.test.js
        │   │   ├── xx.css
        │   │   └── xxx.txt
        │   └── xdr.js
        ├── animation/
        │   ├── animation.js
        │   ├── bezier.js
        │   ├── bounce.js
        │   ├── decelerate.js
        │   ├── demo/
        │   │   └── easeout.html
        │   ├── easein.js
        │   ├── easeinout.js
        │   ├── easeout.js
        │   ├── linear.js
        │   └── test/
        │       ├── animation.test.html
        │       └── animation.test.js
        ├── audio/
        │   ├── audio.js
        │   ├── demo/
        │   │   ├── a.aac
        │   │   ├── a.amr
        │   │   └── audio.html
        │   ├── platform/
        │   │   ├── audio.js
        │   │   └── audio.patch.js
        │   └── test/
        │       ├── audio.test.html
        │       └── audio.test.js
        ├── cache/
        │   ├── abstract.js
        │   ├── cache.js
        │   ├── cache.list.base.js
        │   ├── cache.list.js
        │   ├── cache.share.js
        │   ├── cookie.js
        │   ├── database.js
        │   ├── demo/
        │   │   ├── cookie.html
        │   │   ├── list.html
        │   │   ├── list.js
        │   │   └── storage.html
        │   ├── list.js
        │   ├── manager.js
        │   ├── platform/
        │   │   ├── storage.js
        │   │   └── storage.patch.js
        │   ├── share.js
        │   ├── storage.js
        │   └── test/
        │       ├── cache.custom.js
        │       ├── cache.list.custom.js
        │       ├── cache.test.html
        │       ├── cache.test.js
        │       ├── cookie.test.html
        │       ├── cookie.test.js
        │       ├── storage.test.html
        │       └── storage.test.js
        ├── calendar/
        │   └── calendar.js
        ├── chain/
        │   ├── NodeList.js
        │   ├── README.md
        │   ├── chainable.js
        │   └── test/
        │       ├── chainable.test.html
        │       ├── chainable.test.js
        │       ├── fixture.test.html
        │       ├── playjs.sublime-project
        │       └── playjs.sublime-workspace
        ├── chart/
        │   └── chart.js
        ├── clipboard/
        │   ├── clipboard.js
        │   ├── demo/
        │   │   └── clipboard.html
        │   └── test/
        │       ├── clipboard.test.html
        │       └── clipboard.test.js
        ├── clipper/
        │   ├── clipper.js
        │   └── demo/
        │       └── clipper.html
        ├── clock/
        │   └── clock.js
        ├── color/
        │   ├── color.js
        │   └── demo/
        │       └── color.html
        ├── counter/
        │   ├── counter.js
        │   ├── demo/
        │   │   └── counter.html
        │   ├── platform/
        │   │   ├── counter.js
        │   │   └── counter.patch.js
        │   └── test/
        │       ├── counter.test.html
        │       └── counter.test.js
        ├── cursor/
        │   ├── cursor.js
        │   ├── demo/
        │   │   └── cursor.html
        │   └── platform/
        │       ├── cursor.js
        │       └── cursor.patch.js
        ├── cycler/
        │   ├── cycler.js
        │   └── test/
        │       ├── cycler.test.html
        │       └── cycler.test.js
        ├── data/
        │   ├── portrait/
        │   │   └── portrait.js
        │   └── region/
        │       └── zh.js
        ├── dispatcher/
        │   ├── dispatcher.2.js
        │   ├── dispatcher.js
        │   ├── dsp/
        │   │   ├── group.js
        │   │   ├── node.js
        │   │   ├── single.js
        │   │   └── util.js
        │   ├── module.base.js
        │   ├── module.js
        │   ├── platform/
        │   │   ├── dispatcher.js
        │   │   └── dispatcher.patch.js
        │   ├── regularModule.js
        │   ├── test/
        │   │   ├── c/
        │   │   │   ├── c1.js
        │   │   │   ├── c2.js
        │   │   │   ├── index.css
        │   │   │   └── index.js
        │   │   ├── dispatcher.2.test.html
        │   │   ├── dispatcher.2.test.js
        │   │   ├── m/
        │   │   │   ├── a.html
        │   │   │   ├── b.html
        │   │   │   ├── c.html
        │   │   │   ├── c1.html
        │   │   │   ├── c2.html
        │   │   │   └── root.html
        │   │   ├── private.module.test.html
        │   │   ├── private.module.test.js
        │   │   └── root/
        │   │       ├── index.css
        │   │       └── index.js
        │   └── test.js
        ├── dragger/
        │   ├── dragger.js
        │   ├── simple.js
        │   └── test/
        │       ├── dragger.test.html
        │       └── dragger.test.js
        ├── editor/
        │   ├── area.js
        │   ├── command/
        │   │   ├── backcolor.js
        │   │   ├── blockquote.js
        │   │   ├── bold.js
        │   │   ├── card.js
        │   │   ├── color.js
        │   │   ├── font.js
        │   │   ├── fontname.js
        │   │   ├── fontsize.js
        │   │   ├── forecolor.js
        │   │   ├── format.js
        │   │   ├── insertorderedlist.js
        │   │   ├── insertunorderedlist.js
        │   │   ├── italic.js
        │   │   ├── justifycenter.js
        │   │   ├── justifyleft.js
        │   │   ├── justifyright.js
        │   │   ├── link.js
        │   │   ├── removeformat.js
        │   │   ├── simple.js
        │   │   ├── space.js
        │   │   ├── strikethrough.js
        │   │   ├── superscript.js
        │   │   ├── underline.js
        │   │   └── uploadimage.js
        │   ├── command.js
        │   ├── demo/
        │   │   └── text.html
        │   ├── editor.js
        │   ├── platform/
        │   │   ├── editor.js
        │   │   ├── editor.patch.js
        │   │   ├── editor.td.js
        │   │   └── text.js
        │   ├── text.html
        │   ├── text.js
        │   └── toolbar.js
        ├── effect/
        │   ├── api.js
        │   ├── effect.api.js
        │   ├── effect.js
        │   ├── platform/
        │   │   ├── effect.api.js
        │   │   ├── effect.api.patch.js
        │   │   ├── effect.js
        │   │   └── effect.patch.js
        │   └── test/
        │       ├── effcet.api.test.html
        │       ├── effect.api.test.js
        │       ├── effect.test.html
        │       └── effect.test.js
        ├── encode/
        │   ├── base64.js
        │   ├── crc32.js
        │   ├── demo/
        │   │   ├── json.html
        │   │   └── md5.html
        │   ├── json.js
        │   ├── md5.js
        │   ├── platform/
        │   │   ├── 3rd.json.js
        │   │   ├── json.js
        │   │   └── json.patch.js
        │   ├── sha.md5.js
        │   └── test/
        │       ├── base64.test.html
        │       └── base64.test.js
        ├── es/
        │   ├── array.js
        │   ├── demo/
        │   │   └── array.html
        │   └── platform/
        │       ├── array.js
        │       └── array.patch.js
        ├── event/
        │   ├── esb.js
        │   └── event.js
        ├── event.js
        ├── file/
        │   ├── demo/
        │   │   ├── file.html
        │   │   ├── paste.html
        │   │   ├── select.html
        │   │   └── upload
        │   ├── paste.js
        │   ├── platform/
        │   │   ├── paste.js
        │   │   ├── paste.patch.js
        │   │   ├── select.js
        │   │   └── select.patch.js
        │   ├── save.js
        │   ├── select.js
        │   └── test/
        │       ├── save.test.html
        │       ├── save.test.js
        │       ├── select.test.html
        │       └── select.test.js
        ├── flash/
        │   ├── flash.html
        │   ├── flash.js
        │   ├── platform/
        │   │   ├── flash.js
        │   │   └── flash.patch.js
        │   └── test/
        │       ├── flash.test.html
        │       └── flash.test.js
        ├── focus/
        │   ├── focus.js
        │   ├── platform/
        │   │   ├── focus.js
        │   │   └── focus.patch.js
        │   └── test/
        │       ├── focus.test.html
        │       └── focus.test.js
        ├── form/
        │   ├── demo/
        │   │   └── form.html
        │   ├── form.js
        │   └── test/
        │       ├── form.test.html
        │       └── form.test.js
        ├── gestrue/
        │   ├── drag.js
        │   ├── gestrue.js
        │   ├── pinch.js
        │   ├── rotate.js
        │   ├── swipe.js
        │   └── tap.js
        ├── helper/
        │   └── select.js
        ├── highlight/
        │   ├── test/
        │   │   ├── highlight.test.html
        │   │   └── highlight.test.js
        │   └── touch.js
        ├── history/
        │   ├── history.js
        │   ├── history.override.js
        │   ├── manager.js
        │   └── platform/
        │       ├── history.js
        │       └── history.patch.js
        ├── hover/
        │   ├── hover.js
        │   ├── platform/
        │   │   ├── hover.js
        │   │   └── hover.patch.js
        │   └── test/
        │       ├── hover.test.html
        │       └── hover.test.js
        ├── lazy/
        │   ├── demo/
        │   │   └── image.html
        │   ├── image.js
        │   └── loading.js
        ├── lightbox/
        │   ├── demo/
        │   │   └── lightbox.html
        │   └── lightbox.js
        ├── list/
        │   ├── demo/
        │   │   ├── data.js
        │   │   ├── list/
        │   │   │   ├── cache.js
        │   │   │   ├── item.js
        │   │   │   ├── jst.html
        │   │   │   ├── ntp.html
        │   │   │   ├── pg.html
        │   │   │   └── wf.html
        │   │   ├── page.html
        │   │   └── waterfall.html
        │   ├── holder.js
        │   ├── module.js
        │   ├── module.pager.js
        │   ├── module.waterfall.js
        │   ├── page.js
        │   ├── test/
        │   │   ├── cache.list.custom.js
        │   │   ├── module.pager.test.html
        │   │   └── module.pager.test.js
        │   └── waterfall.js
        ├── media/
        │   ├── audio.js
        │   ├── flash.js
        │   ├── media.js
        │   ├── playlist.js
        │   └── test/
        │       ├── audio.test.html
        │       └── audio.test.js
        ├── page/
        │   ├── base.js
        │   ├── page.base.js
        │   ├── page.js
        │   ├── page.simple.js
        │   └── simple.js
        ├── placeholder/
        │   ├── demo/
        │   │   └── placeholder.html
        │   ├── placeholder.js
        │   ├── platform/
        │   │   ├── holder.js
        │   │   └── holder.patch.js
        │   └── test/
        │       ├── placeholder.test.html
        │       └── placeholder.test.js
        ├── profile/
        │   └── profile.js
        ├── query/
        │   ├── demo/
        │   │   └── demo.html
        │   ├── nes.js
        │   └── query.js
        ├── range/
        │   ├── demo/
        │   │   └── range.html
        │   ├── range.js
        │   └── test/
        │       ├── range.test.html
        │       └── range.test.js
        ├── region/
        │   ├── demo/
        │   │   └── at.html
        │   ├── region.zh.js
        │   ├── test/
        │   │   ├── region.zh.test.html
        │   │   └── region.zh.test.js
        │   └── zh.js
        ├── resize/
        │   ├── demo/
        │   │   └── resize.html
        │   ├── resize.js
        │   └── test/
        │       ├── resize.test.html
        │       └── resize.test.js
        ├── scroll/
        │   ├── demo/
        │   │   ├── simple.html
        │   │   └── smart.html
        │   ├── platform/
        │   │   ├── simple.js
        │   │   └── simple.patch.js
        │   ├── scroll.simple.js
        │   ├── simple.js
        │   └── smart.js
        ├── selector/
        │   ├── cascade.js
        │   ├── demo/
        │   │   ├── cascade.html
        │   │   └── selector.html
        │   ├── range.js
        │   ├── selector.js
        │   └── selector.range.js
        ├── slider/
        │   ├── demo/
        │   │   ├── simple.html
        │   │   └── y.html
        │   ├── simple.js
        │   ├── slider.js
        │   ├── slider.simple.js
        │   ├── slider.x.js
        │   ├── slider.xy.js
        │   ├── slider.y.js
        │   ├── test/
        │   │   ├── slider.test.html
        │   │   ├── slider.test.js
        │   │   └── sorter.test.js
        │   ├── x.js
        │   ├── xy.js
        │   └── y.js
        ├── sort/
        │   ├── demo/
        │   │   ├── horizontal.html
        │   │   ├── horizontal.trigger.html
        │   │   ├── vertical.html
        │   │   └── vertical.trigger.html
        │   ├── horizontal.js
        │   ├── sortable.js
        │   └── vertical.js
        ├── suggest/
        │   ├── at.js
        │   ├── demo/
        │   │   ├── at.html
        │   │   └── suggest.html
        │   ├── suggest.js
        │   └── test/
        │       ├── suggest.test.html
        │       └── suggest.test.js
        ├── tab/
        │   ├── tab.js
        │   ├── tab.view.js
        │   ├── test/
        │   │   ├── tab.test.html
        │   │   └── tab.test.js
        │   └── view.js
        ├── template/
        │   ├── demo/
        │   │   ├── a.css
        │   │   ├── a.html
        │   │   ├── a.js
        │   │   ├── b.css
        │   │   ├── b.html
        │   │   ├── b.js
        │   │   ├── jst.html
        │   │   └── tpl.html
        │   ├── jst.js
        │   ├── test/
        │   │   ├── jst.test.html
        │   │   ├── jst.test.js
        │   │   ├── myItem.js
        │   │   ├── tpl.test.html
        │   │   └── tpl.test.js
        │   ├── tpl.js
        │   └── trimpath.js
        ├── timer/
        │   ├── animation.js
        │   ├── demo/
        │   │   ├── a.js
        │   │   ├── output.js
        │   │   ├── test.html
        │   │   └── test.min.html
        │   ├── interval.js
        │   └── platform/
        │       ├── animation.js
        │       └── animation.patch.js
        └── toggle/
            ├── demo/
            │   └── toggle.html
            ├── test/
            │   ├── toggle.test.html
            │   └── toggle.test.js
            └── toggle.js
Download .txt
SYMBOL INDEX (30 symbols across 5 files)

FILE: src/util/chain/NodeList.js
  function _$$NodeList (line 212) | function _$$NodeList(_selector, _context){

FILE: src/util/color/color.js
  function toHex (line 76) | function toHex(n) {

FILE: src/util/encode/crc32.js
  function k (line 25) | function k(string){string=string.replace(/\r\n/g,"\n");var F="";for(var ...

FILE: src/util/encode/platform/3rd.json.js
  function v (line 3) | function v(a){if("bug-string-char-index"==a)return"a"!="a"[0];var f,c="j...

FILE: src/util/query/nes.js
  function Parser (line 189) | function Parser(opts) {
  function parse (line 524) | function parse(sl) {
  function checkNth (line 563) | function checkNth(node, type) {
  function nthChild (line 569) | function nthChild(node, type) {
  function nthLastChild (line 576) | function nthLastChild(node,type) {
  function nthPrev (line 583) | function nthPrev(node, type) {
  function nthNext (line 592) | function nthNext(node, type) {
  function getAttribute (line 600) | function getAttribute(node, key) {
  function distinct (line 617) | function distinct(array) {
  function siblingCheck (line 676) | function siblingCheck(a, b) {
  function uniqueSort (line 689) | function uniqueSort(nodeList) {
  function createNthFilter (line 710) | function createNthFilter(isNext, isType) {
  function clearNthPositionCache (line 756) | function clearNthPositionCache() {
  function matchDatum (line 992) | function matchDatum(node, datum, ignored) {
  function matchData (line 1006) | function matchData(node, data, ignored) { // 稍后再看存入step
  function createMatch (line 1025) | function createMatch(data) {
  function createMatches (line 1032) | function createMatches(data) {
  function filter (line 1044) | function filter(results, data, ignored) {
  function getTargets (line 1061) | function getTargets(data, context) {
  function find (line 1088) | function find(datas, context) {
  function get (line 1113) | function get(sl, context) {
  function one (line 1126) | function one(sl, context) {
  function all (line 1145) | function all(sl, context) {
  function matches (line 1168) | function matches(node, sl) {
  function matchOneData (line 1182) | function matchOneData(node, data) {
  function nthMatch (line 1296) | function nthMatch(first) {
Condensed preview — 639 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (2,921K chars).
[
  {
    "path": ".gitignore",
    "chars": 30,
    "preview": ".settings\n.project\n.svn\n.idea/"
  },
  {
    "path": "CHANGELOG",
    "chars": 7378,
    "preview": "0.5.2   (2019-01-30)\n--------------------\n- 功能新增\n  * util/ajax/dwr 在请求异常时控制台输出请求信息\n- BUG修复模块\n  * 修正低版本跨域代理文件发送请求时先序列化对象\n"
  },
  {
    "path": "LICENSE",
    "chars": 1249,
    "preview": "NEJ is released under the MIT license\n\nCopyright 2012 (c) NetEase, Inc\n\nUses \nTrimPath  (http://code.google.com/p/trimpa"
  },
  {
    "path": "README.md",
    "chars": 1684,
    "preview": "# NEJ - JavaScript Framework\n\n## 概述 \n\n跨平台WEB前端开发框架,主要提供Web端SDK用于开发Web应用,服务器端SDK用于整合解决方案的服务器端实现\n\n主要特性包括:\n\n* [依赖管理系统](./do"
  },
  {
    "path": "bower.json",
    "chars": 485,
    "preview": "{\n    \"name\": \"nej\",\n    \"version\": \"0.5.2\",\n    \"homepage\": \"https://github.com/genify/nej\",\n    \"authors\": [\n        \""
  },
  {
    "path": "doc/AJAX.md",
    "chars": 13860,
    "preview": "# 前后端通讯系统\n\n## 概述\n\n随着富媒体应用的不断发展成熟,AJAX的应用得到越来越多的普及,而AJAX应用的一个核心功能-异步数据载入功能也就显得越为重要,包括HTML5的发展也为此提供了强大的支持。\n\n对于富媒体WEB应用来说异步"
  },
  {
    "path": "doc/CACHE.md",
    "chars": 56,
    "preview": "# 前端存储机制\n\n## 概述\n\n\n\n## 内存缓存\n\n\n\n\n## 本地存储\n\n\n\n\n## 本地数据库\n\n\n\n\n"
  },
  {
    "path": "doc/DEPENDENCY.md",
    "chars": 8685,
    "preview": "# 依赖管理系统\n\n## 概述\n\n随着前端系统的日渐复杂,系统所依赖的文件越来越多,单纯的依靠人肉管理已经无法满足高品质系统的需求了,为了便于开发者管理、老系统平滑升级迁移,NEJ提供了一套文件依赖管理系统,本系统主要特性包括:\n\n* 安全"
  },
  {
    "path": "doc/DISPATCHER.md",
    "chars": 19136,
    "preview": "# 模块调度系统\n\n## 概述\n\n随着WEB技术的发展,使用WEB技术构建富应用的场景越来越多,相比于纯Native技术,利用WEB技术构建富应用有其先天优势,如良好的跨平台特性、高效的构建及需求响应效率、更低的资源成本投入等,随着移动互联"
  },
  {
    "path": "doc/FAQ.md",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "doc/MESSAGE.md",
    "chars": 8326,
    "preview": "# 跨域窗体消息系统\n\n## 概述\n\n由于同源策略的限制,非同域的窗体之间不能直接进行数据通讯或共享,因此我们引入这部分内容来分析解决非同域及同域的窗体之间的数据通讯或共享问题。\n\n这里的消息通讯是指应用在客户端跨窗体之间的消息交互,可以保"
  },
  {
    "path": "doc/PLATFORM.md",
    "chars": 11186,
    "preview": "# 平台适配系统\n\n## 概述\n\n随着WEB技术的发展,越来越多的平台开始使用WEB技术来构建系统,NEJ框架做为一套跨平台的技术解决方案框架集成了基于AOP思想的平台适配策略,该策略具有以下特性:\n\n* 屏蔽平台差异\n* 松耦合性(通过配"
  },
  {
    "path": "doc/TEMPLATE.md",
    "chars": 15081,
    "preview": "# 模板系统\n\n## 概述\n\n模板系统主要用来分离视图与数据,用以生成特定格式的文档,可以提升开发效率,良好的设计也可以使得代码重用变得更加容易,主要特点包括:\n\n* 分离代码(业务逻辑代码与视图代码)\n* 数据分离(动态数据与静态数据)\n"
  },
  {
    "path": "doc/WIDGET.md",
    "chars": 21270,
    "preview": "# 控件系统\n\n## 概述\n\n控件系统主要用来解决系统复杂性的问题,使得系统不会因为变得复杂而不可控,同时保证其维护性和扩展性\n\nNEJ框架提供了基于常规面向对象的思想构建的控件系统,主要用于:\n\n* 提供通用解决方案的封装支持\n* 提供核"
  },
  {
    "path": "doc/guide/Build.Scalable.Web.System/Module.md",
    "chars": 19704,
    "preview": "# 构建高可伸缩性的WEB交互式系统(2) - 模块的可伸缩性\n\n## 概述\n\n可伸缩性是一种对软件系统处理能力的设计指标,高可伸缩性代表一种弹性,在系统扩展过程中,能够保证旺盛的生命力,通过很少的改动,就能实现整个系统处理能力的增长。\n\n"
  },
  {
    "path": "doc/guide/Build.Scalable.Web.System/Platform.md",
    "chars": 7720,
    "preview": "# 构建高可伸缩性的WEB交互式系统(1) - 平台的可伸缩性\n\n## 概述\n\n可伸缩性是一种对软件系统处理能力的设计指标,高可伸缩性代表一种弹性,在系统扩展过程中,能够保证旺盛的生命力,通过很少的改动,就能实现整个系统处理能力的增长。\n\n"
  },
  {
    "path": "doc/guide/Build.Scalable.Web.System.md",
    "chars": 25796,
    "preview": "# 构建高可伸缩性的WEB交互式系统\n\n## 概述\n\n可伸缩性是一种对软件系统处理能力的设计指标,高可伸缩性代表一种弹性,在系统扩展过程中,能够保证旺盛的生命力,通过很少的改动,就能实现整个系统处理能力的增长。\n\n在系统设计的时候,充分地考"
  },
  {
    "path": "package.json",
    "chars": 634,
    "preview": "{\n    \"name\": \"nej-framework\",\n    \"description\": \"JavaScript Cross-Platform Framework\",\n    \"version\": \"0.5.2\",\n    \"li"
  },
  {
    "path": "res/crossdomain.xml",
    "chars": 185,
    "preview": "<?xml version=\"1.0\"?>\n<cross-domain-policy> \n  <site-control permitted-cross-domain-policies=\"by-content-type\"/> \n  <all"
  },
  {
    "path": "res/nej_proxy_frame.html",
    "chars": 3466,
    "preview": "<html> <head> <title>NEJ AJAX PROXY</title> <meta charset=\"utf-8\"/> \n<script>window.whitelist=[/.*/i];</script> \n<script"
  },
  {
    "path": "res/nej_proxy_upload.html",
    "chars": 491,
    "preview": "<html><body>\n<!-- 将上传结果序列化为JSON串后填入result中即可 -->\n<script>\n    window.result = '{\"url\":\"http://192.168.146.84:1122/xhr/3."
  },
  {
    "path": "res/src/nej_proxy_frame.html",
    "chars": 10155,
    "preview": "<html>\n  <head>\n    <title>NEJ AJAX PROXY</title>\n    <meta charset=\"utf-8\"/>\n    <script>window.whitelist = [/.*/i];</s"
  },
  {
    "path": "res/src/nej_proxy_upload.html",
    "chars": 491,
    "preview": "<html><body>\n<!-- 将上传结果序列化为JSON串后填入result中即可 -->\n<script>\n    window.result = '{\"url\":\"http://192.168.146.84:1122/xhr/3."
  },
  {
    "path": "src/base/.dep",
    "chars": 156,
    "preview": "基础库依赖关系\n\nglobal    <-    klass\n          <-    platform\n          <-    config      <-    constant\n          <-    util "
  },
  {
    "path": "src/base/chain.js",
    "chars": 1473,
    "preview": "/*\n * ------------------------------------------\n * 可链式接口实现代理文件\n * @version  1.0\n * @author   genify(caijf@corp.netease."
  },
  {
    "path": "src/base/config.js",
    "chars": 3675,
    "preview": "/*\n * ------------------------------------------\n * 平台配置信息\n * @version  1.0\n * @author   genify(caijf@corp.netease.com)\n"
  },
  {
    "path": "src/base/constant.js",
    "chars": 2351,
    "preview": "/*\n * ------------------------------------------\n * 常量定义文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.com)\n"
  },
  {
    "path": "src/base/demo/define/a.css",
    "chars": 34,
    "preview": ".a{color:#f00;}\n.b{color:#0000FF;}"
  },
  {
    "path": "src/base/demo/define/a.js",
    "chars": 22,
    "preview": "console.log('aaaaa');\n"
  },
  {
    "path": "src/base/demo/define/a.json",
    "chars": 46,
    "preview": "{\n    \"a\":\"aaaaaaaaa\",\n    \"b\":\"bbbbbbbbbb\"\n}\n"
  },
  {
    "path": "src/base/demo/define/define.html",
    "chars": 678,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>define demo</title>\n    <meta charset=\"utf-8\" />\n    <script>\n      function "
  },
  {
    "path": "src/base/demo/define/style.html",
    "chars": 696,
    "preview": "<!DOCTYPE html>\n<html>\n    <head>\n        <title>define demo</title>\n        <meta charset=\"utf-8\" />\n        <script>\n "
  },
  {
    "path": "src/base/demo/element.html",
    "chars": 885,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>element demo</title>\n    <meta charset=\"utf-8\" />\n    <script>\n      function"
  },
  {
    "path": "src/base/demo/event.html",
    "chars": 1911,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>element demo</title>\n    <meta charset=\"utf-8\" />\n    <script>\n      function"
  },
  {
    "path": "src/base/demo/klass.html",
    "chars": 1525,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>klass demo</title>\n    <meta charset=\"utf-8\" />\n    <script>\n      function l"
  },
  {
    "path": "src/base/demo/test.html",
    "chars": 1441,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>klass demo</title>\n    <meta charset=\"utf-8\" />\n    <script>\n        function l"
  },
  {
    "path": "src/base/demo/util.html",
    "chars": 863,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>util demo</title>\n    <meta charset=\"utf-8\" />\n    <script>\n      function lo"
  },
  {
    "path": "src/base/element.js",
    "chars": 59761,
    "preview": "/*\n * ------------------------------------------\n * 节点接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.co"
  },
  {
    "path": "src/base/event.js",
    "chars": 24016,
    "preview": "/*\n * ------------------------------------------\n * 事件接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.com"
  },
  {
    "path": "src/base/global.js",
    "chars": 4691,
    "preview": "/*\n * --------------------------------------------\n * 原生对象扩展接口\n * @version 1.0\n * @author  genify(caijf@corp.netease.com"
  },
  {
    "path": "src/base/klass.js",
    "chars": 7506,
    "preview": "/*\n * --------------------------------------------\n * NEJ类模型\n * @version  1.0\n * @author   genify(caijf@corp.netease.com"
  },
  {
    "path": "src/base/platform/config.js",
    "chars": 3167,
    "preview": "/*\n * ------------------------------------------\n * 平台适配接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/base/platform/config.patch.js",
    "chars": 701,
    "preview": "/*\n * ------------------------------------------\n * 平台适配接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/base/platform/element.js",
    "chars": 10820,
    "preview": "/*\n * ------------------------------------------\n * 平台适配接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/base/platform/element.patch.js",
    "chars": 13996,
    "preview": "/*\n * ------------------------------------------\n * 平台适配接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/base/platform/event.js",
    "chars": 3729,
    "preview": "/*\n * ------------------------------------------\n * 平台适配接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/base/platform/event.patch.js",
    "chars": 11059,
    "preview": "/*\n * ------------------------------------------\n * 平台适配接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/base/platform/util.js",
    "chars": 1449,
    "preview": "/*\n * ------------------------------------------\n * 平台适配接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/base/platform/util.patch.js",
    "chars": 2073,
    "preview": "/*\n * ------------------------------------------\n * 平台适配接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/base/platform.js",
    "chars": 6295,
    "preview": "/*\n * ------------------------------------------\n * 平台接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.com"
  },
  {
    "path": "src/base/test/config.test.html",
    "chars": 657,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>config测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"http"
  },
  {
    "path": "src/base/test/config.test.js",
    "chars": 1576,
    "preview": "window.NEJ_CONF = {\n    'root':'/res/',\n    'p_frame':['http://nei.netease.com/html/nej_proxy_frame.html'],\n    'p_flash"
  },
  {
    "path": "src/base/test/constant.test.html",
    "chars": 663,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>constant测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"ht"
  },
  {
    "path": "src/base/test/constant.test.js",
    "chars": 1400,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"constant\");\n    var _p = NEJ.P('nej.g'),\n        _g = NEJ.P('nej.p');\n\n    "
  },
  {
    "path": "src/base/test/element.test.html",
    "chars": 1250,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>element测试页面</title>\n  \t\t<link rel=\"stylesheet\" href=\"ht"
  },
  {
    "path": "src/base/test/element.test.js",
    "chars": 10945,
    "preview": "var f = function(){\n    //定义测试模块\n    module('element');\n    var p = NEJ.P('nej.p');\n    var e = NEJ.P('nej.e');\n    var "
  },
  {
    "path": "src/base/test/event.test.html",
    "chars": 1045,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>event.js测试页面</title>\n  \t\t<link rel=\"stylesheet\" href=\"h"
  },
  {
    "path": "src/base/test/event.test.js",
    "chars": 4429,
    "preview": "var f = function(){\n\t//定义测试模块\n\tmodule('event');\n\tvar p = NEJ.P('nej.p');\n\tvar e = NEJ.P('nej.e');\n\tvar v = NEJ.P('nej.v'"
  },
  {
    "path": "src/base/test/global.test.html",
    "chars": 659,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>global测试页面</title>\n  \t\t<link rel=\"stylesheet\" href=\"htt"
  },
  {
    "path": "src/base/test/global.test.js",
    "chars": 6361,
    "preview": "var f = function(){\n    //定义测试模块\n    module('global');\n\n    //开始单元测试\n    test('Function prototype',function(){\n        e"
  },
  {
    "path": "src/base/test/klass.test.html",
    "chars": 656,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>klass测试页面</title>\n  \t\t<link rel=\"stylesheet\" href=\"http"
  },
  {
    "path": "src/base/test/klass.test.js",
    "chars": 1688,
    "preview": "var f = function(_k){\n    //定义测试模块\n    module('global');\n\n    //开始单元测试\n    test('NEJ.C',function(){\n        var _f = _k."
  },
  {
    "path": "src/base/test/platform.test.html",
    "chars": 663,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>platform测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"ht"
  },
  {
    "path": "src/base/test/platform.test.js",
    "chars": 356,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"platform\");\n    var _p = NEJ.P('nej.p');\n\n    //开始单元测试\n    test('NEJ配置文件测试'"
  },
  {
    "path": "src/base/test/util.test.html",
    "chars": 683,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>util测试页面</title>\n  \t\t<link rel=\"stylesheet\" href=\"http:"
  },
  {
    "path": "src/base/test/util.test.js",
    "chars": 6803,
    "preview": "var f = function(){\n    //定义测试模块\n    module('util');\n    var _util = NEJ.P('nej.u');\n    //开始单元测试\n    test('util',functi"
  },
  {
    "path": "src/base/util.js",
    "chars": 38291,
    "preview": "/*\n * ------------------------------------------\n * 通用接口实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.com"
  },
  {
    "path": "src/define.js",
    "chars": 40187,
    "preview": "(function(d,p){\n    var __config = {root:{/*lib,pro,platform*/}/*native,charset,global,platform*/},\n        __xqueue  = "
  },
  {
    "path": "src/ui/audio/audio.css",
    "chars": 23136,
    "preview": ".#<uispace>{display:$<box>;$<box-pack>:end;width:150px;height:30px;line-height:30px;overflow:hidden;text-align:left;}\n.#"
  },
  {
    "path": "src/ui/audio/audio.html",
    "chars": 88,
    "preview": "<div>\n\t<marquee class=\"z-ttl\"></marquee>\n\t<div class=\"z-act js-play\">&nbsp;</div>\n</div>"
  },
  {
    "path": "src/ui/audio/audio.js",
    "chars": 4453,
    "preview": "/*\n * ------------------------------------------\n * 音频播放器实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.co"
  },
  {
    "path": "src/ui/audio/mp3.css",
    "chars": 3172,
    "preview": ".#<uispace> .m-pre, .#<uispace> .m-play, .#<uispace> .m-next, .#<uispace> .m-cur, .#<uispace> .m-pause, .#<uispace> .m-v"
  },
  {
    "path": "src/ui/audio/mp3.html",
    "chars": 1134,
    "preview": "<div class=\"m-cnt\">\n\t<div class=\"cse\">\n\t  <div class=\"m-player\">\n\t    <div class=\"ctl\">\n\t      <span class=\"f-ib m-pre\" "
  },
  {
    "path": "src/ui/audio/mp3.js",
    "chars": 12170,
    "preview": "/*\n * ------------------------------------------\n * 音频播放器实现文件\n * @version  1.0\n * @author   cheng-lin(cheng-lin@corp.net"
  },
  {
    "path": "src/ui/audio/test/audio.test.html",
    "chars": 753,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>audio测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"http:"
  },
  {
    "path": "src/ui/audio/test/audio.test.js",
    "chars": 675,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"audio\");\n    //开始单元测试\n    test('audio',function(){\n        expect(0);\n     "
  },
  {
    "path": "src/ui/audio/test/mp3.test.html",
    "chars": 686,
    "preview": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>mp3测试页</title>\n\t\t<link rel=\"stylesheet\" href=\"http://"
  },
  {
    "path": "src/ui/audio/test/mp3.test.js",
    "chars": 1744,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"ui-mp3\");\n    var _  = NEJ.P,\n        _e = _('nej.e'),\n        _p = _('nej."
  },
  {
    "path": "src/ui/base.js",
    "chars": 7159,
    "preview": "/*\n * ------------------------------------------\n * UI控件基类实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/carousel/carousel.js",
    "chars": 22358,
    "preview": "/*\n * ------------------------------------------\n * 卡片播放器实现文件\n * @version  1.0\n * @author   huxueliang(huxueliang@corp"
  },
  {
    "path": "src/ui/carousel/carousel.list.js",
    "chars": 134,
    "preview": "// link to ui/carousel/list for compatible\n// use ui/carousel/list for new project\nNEJ.define(['./list.js'],function(_t)"
  },
  {
    "path": "src/ui/carousel/carousel.x.js",
    "chars": 126,
    "preview": "// link to ui/carousel/x for compatible\n// use ui/carousel/x for new project\nNEJ.define(['./x.js'],function(_t){return _"
  },
  {
    "path": "src/ui/carousel/carousel.y.js",
    "chars": 125,
    "preview": "// link to ui/carousel/y for compatible\n// use ui/carousel/y for new project\nNEJ.define(['./y.js'],function(_t){return _"
  },
  {
    "path": "src/ui/carousel/indicator.js",
    "chars": 2668,
    "preview": "/*\n * ------------------------------------------\n * 指示器对象实现文件\n * @version  1.0\n * @author   huxueliang(huxueliang@corp.n"
  },
  {
    "path": "src/ui/carousel/list.js",
    "chars": 5590,
    "preview": "/*\n * ------------------------------------------\n * 卡片播放器列表实现文件\n * @version  1.0\n * @author   huxueliang(huxueliang@corp"
  },
  {
    "path": "src/ui/carousel/test/carousel.test.html",
    "chars": 1002,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<meta name=\"viewport\" content=\"width=device-width,user-scalabl"
  },
  {
    "path": "src/ui/carousel/test/carousel.test.js",
    "chars": 1096,
    "preview": "var f = function(){\n\t//定义测试模块\n\tmodule(\"carousel\");\n\tvar _  = NEJ.P,\n        _v = _('nej.v'),\n\t\t_e = _('nej.e'),\n\t\t_p = _"
  },
  {
    "path": "src/ui/carousel/x.js",
    "chars": 1018,
    "preview": "/*\n * ------------------------------------------\n * 水平模块播放器实现文件\n * @version  1.0\n * @author   huxueliang(huxueliang@corp"
  },
  {
    "path": "src/ui/carousel/y.js",
    "chars": 1020,
    "preview": "/*\n * ------------------------------------------\n * 卡片垂直播放器实现文件\n * @version  1.0\n * @author   huxueliang(huxueliang@corp"
  },
  {
    "path": "src/ui/carousel.list/carousel.list.js",
    "chars": 142,
    "preview": "// link to ui/carousel/list for compatible\n// use ui/carousel/list for new project\nNEJ.define(['ui/carousel/list'],func"
  },
  {
    "path": "src/ui/colorpick/colorpanel.css",
    "chars": 779,
    "preview": ".#<uispace>{width:160px;margin:0 auto;overflow:hidden;$<user-select>:none;}\n.#<uispace> .zbg{background:url(#<root>nej_c"
  },
  {
    "path": "src/ui/colorpick/colorpanel.html",
    "chars": 274,
    "preview": "<div>\n  <div class=\"zwrp zpnl js-ztag\">\n    <span class=\"zdot zbg js-ztag\">&nbsp;</span>\n    <div class=\"zshw\">&nbsp;</d"
  },
  {
    "path": "src/ui/colorpick/colorpanel.js",
    "chars": 5943,
    "preview": "/*\n * ------------------------------------------\n * 颜色面板控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/colorpick/colorpick.complex.js",
    "chars": 145,
    "preview": "// link to ui/colorpick/complex for compatible\n// use ui/colorpick/complex for new project\nNEJ.define(['./complex.js'],f"
  },
  {
    "path": "src/ui/colorpick/colorpick.css",
    "chars": 553,
    "preview": ".#<uispace> .zbx{background:url(<#root>nej_color_btn.png) no-repeat -50px -50px;}\n.#<uispace> .zinf{width:160px;margin:0"
  },
  {
    "path": "src/ui/colorpick/colorpick.html",
    "chars": 306,
    "preview": "<div>\n  <div class=\"zinf\">\n    <span class=\"zfl zes zbx js-ztag\" title=\"清除颜色\">&nbsp;</span>\n    <span class=\"zfl zpv js-"
  },
  {
    "path": "src/ui/colorpick/colorpick.js",
    "chars": 5857,
    "preview": "/*\n * ------------------------------------------\n * 颜色选择控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/colorpick/colorpick.simple.js",
    "chars": 142,
    "preview": "// link to ui/colorpick/simple for compatible\n// use ui/colorpick/simple for new project\nNEJ.define(['./simple.js'],func"
  },
  {
    "path": "src/ui/colorpick/complex.html",
    "chars": 117,
    "preview": "{list xlist as x}\n<a class=\"zitm zitm2\" style=\"background-color:#${x}\" data-value=\"#${x}\" href=\"#\">&nbsp;</a>\n{/list}"
  },
  {
    "path": "src/ui/colorpick/complex.js",
    "chars": 3329,
    "preview": "/*\n * ------------------------------------------\n * 颜色选择控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/colorpick/demo/colorpanel.html",
    "chars": 1086,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>demo for colorpick</title>\n    <meta charset=\"utf-8\" />\n    <style>\n        b"
  },
  {
    "path": "src/ui/colorpick/demo/colorpick.html",
    "chars": 952,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>demo for colorpick</title>\n    <meta charset=\"utf-8\" />\n    <script>\n      fu"
  },
  {
    "path": "src/ui/colorpick/demo/complex.html",
    "chars": 903,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>demo for colorpick</title>\n    <meta charset=\"utf-8\" />\n    <script>\n        fu"
  },
  {
    "path": "src/ui/colorpick/demo/simple.html",
    "chars": 901,
    "preview": "<!DOCTYPE html>\n<html>\n<head>\n    <title>demo for colorpick</title>\n    <meta charset=\"utf-8\" />\n    <script>\n        fu"
  },
  {
    "path": "src/ui/colorpick/simple.css",
    "chars": 727,
    "preview": ".#<uispace>{text-align:left;}\n.#<uispace> .zdft{display:block;padding:3px 1px 3px 5px;margin:3px;cursor:pointer;}\n.#<uis"
  },
  {
    "path": "src/ui/colorpick/simple.html",
    "chars": 411,
    "preview": "<textarea name='ntp' id='#<seedHtml>'>\n  <div class=\"'+_seed_css+'\">\n    <a class=\"zdft\" title=\"去除颜色\" href=\"#\">\n      <s"
  },
  {
    "path": "src/ui/colorpick/simple.js",
    "chars": 6579,
    "preview": "/*\n * ------------------------------------------\n * 颜色选择控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/colorpick/test/colorpanel.test.html",
    "chars": 740,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>colorpanel测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\""
  },
  {
    "path": "src/ui/colorpick/test/colorpanel.test.js",
    "chars": 838,
    "preview": "var f = function(){\n\t//定义测试模块\n\tmodule(\"colorpanel\");\n\tvar _  = NEJ.P,\n\t\t_e = _('nej.e'),\n\t\t_p = _('nej.ui');\n\t//开始单元测试\n\t"
  },
  {
    "path": "src/ui/datepick/datepick.css",
    "chars": 821,
    "preview": ".#<uispace>{width:210px;border:1px solid #aaa;font-size:14px;text-align:center;}\n.#<uispace> .zact{line-height:30px;over"
  },
  {
    "path": "src/ui/datepick/datepick.html",
    "chars": 627,
    "preview": "<textarea name='jst' id='#<seedDate>'>\n\t<table class=\"zday\">\n\t\t<tr>{list [\"日\",\"一\",\"二\",\"三\",\"四\",\"五\",\"六\"] as x}<th>${x}</th"
  },
  {
    "path": "src/ui/datepick/datepick.js",
    "chars": 5941,
    "preview": "/*\n * ------------------------------------------\n * 日期选择控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/datepick/demo/datepick.html",
    "chars": 1740,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>demo for colorpick</title>\n    <meta charset=\"utf-8\" />\n    <style>\n        #"
  },
  {
    "path": "src/ui/datepick/test/datepick.test.html",
    "chars": 1333,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>datepick测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"ht"
  },
  {
    "path": "src/ui/datepick/test/datepick.test.js",
    "chars": 1725,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"datepick\");\n    var _  = NEJ.P,\n        _e = _('nej.e'),\n        _p = _('ne"
  },
  {
    "path": "src/ui/editor/command/color.complex.js",
    "chars": 155,
    "preview": "// link to ui/editor/command/complex for compatible\n// use ui/editor/command/complex for new project\nNEJ.define(['./comp"
  },
  {
    "path": "src/ui/editor/command/color.css",
    "chars": 71,
    "preview": ".#<uispace>{width:160px;padding:10px 5px 5px;border:1px solid #9FAC87;}"
  },
  {
    "path": "src/ui/editor/command/color.js",
    "chars": 2817,
    "preview": "/*\n * ------------------------------------------\n * 颜色选择卡片实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/editor/command/color.simple.js",
    "chars": 152,
    "preview": "// link to ui/editor/command/simple for compatible\n// use ui/editor/command/simple for new project\nNEJ.define(['./simple"
  },
  {
    "path": "src/ui/editor/command/complex.css",
    "chars": 68,
    "preview": ".#<uispace>{width:238px;padding:5px 0 8px;border:1px solid #9FAC87;}"
  },
  {
    "path": "src/ui/editor/command/complex.js",
    "chars": 1391,
    "preview": "/*\n * ------------------------------------------\n * 颜色选择卡片实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/editor/command/font.css",
    "chars": 392,
    "preview": ".#<uispace>{border:1px solid #9FAC87;font-size:12px;text-align:left;}\n.#<uispace> .zitm{display:block;position:relative;"
  },
  {
    "path": "src/ui/editor/command/font.html",
    "chars": 198,
    "preview": "{list xlist as x}\n<a class=\"zitm\" hidefocus=\"true\" style=\"${style}:${x.style|default:x.name};\" data-index=\"${x_index}\">\n"
  },
  {
    "path": "src/ui/editor/command/font.js",
    "chars": 3588,
    "preview": "/*\n * ------------------------------------------\n * 字体字号选择卡片基类实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netea"
  },
  {
    "path": "src/ui/editor/command/fontname.js",
    "chars": 2240,
    "preview": "/*\n * ------------------------------------------\n * 字体选择控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/editor/command/fontsize.js",
    "chars": 2061,
    "preview": "/*\n * ------------------------------------------\n * 富媒体编辑器字号选择控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.net"
  },
  {
    "path": "src/ui/editor/command/link.css",
    "chars": 733,
    "preview": ".#<uispace>{padding:20px 20px 32px 20px;}\n.#<uispace> .u-title{color:#ccc;height:35px;line-height:35px;border-bottom: so"
  },
  {
    "path": "src/ui/editor/command/link.html",
    "chars": 470,
    "preview": "<div>\n\t<div class=\"u-row f-cb\"><div class=\"u-edit f-cb\"><div class=\"f-fl u-title\">标题</div><input class=\"f-fl ipt\" type=\""
  },
  {
    "path": "src/ui/editor/command/link.js",
    "chars": 5402,
    "preview": "/*\n * ------------------------------------------\n * 超链接卡片实现文件\n * @version  1.0\n * @author   cheng-lin(cheng-lin@corp.net"
  },
  {
    "path": "src/ui/editor/command/simple.css",
    "chars": 64,
    "preview": ".#<uispace>{width:160px;padding:5px 0;border:1px solid #9FAC87;}"
  },
  {
    "path": "src/ui/editor/command/simple.js",
    "chars": 1370,
    "preview": "/*\n * ------------------------------------------\n * 颜色选择卡片实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/editor/command/uploadimage.css",
    "chars": 616,
    "preview": ".#<uispace>{width:336px;}\n.#<uispace> .m-iframe{position:absolute;height:0px;width:0px;left:-9000px;}\n.#<uispace> .u-upl"
  },
  {
    "path": "src/ui/editor/command/uploadimage.html",
    "chars": 783,
    "preview": "<div>\n\t<div>\n\t  <a class=\"u-btn f-ib upload j-tab f-fl\" name=\"upload\"><span class=\"img-upload\">上传图片</span></a>\n\t  <a cla"
  },
  {
    "path": "src/ui/editor/command/uploadimage.js",
    "chars": 11314,
    "preview": "/*\n * ------------------------------------------\n * 富媒体编辑器图片上传控件实现文件\n * @version  1.0\n * @author   cheng-lin(cheng-lin@c"
  },
  {
    "path": "src/ui/editor/custom.js",
    "chars": 3268,
    "preview": "/*\n * ------------------------------------------\n * 自定义富媒体编辑器封装实现文件\n * @version  1.0\n * @author   genify(caijf@corp.nete"
  },
  {
    "path": "src/ui/editor/demo/custom.html",
    "chars": 574,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>test editor</title>\n    <meta charset=\"utf-8\"/>\n      <style>\n          div.m"
  },
  {
    "path": "src/ui/editor/editor.css",
    "chars": 1239,
    "preview": ".#<uispace>{width:500px;border:1px solid #ddd;text-align:center;}\n.#<uispace> .zbg{background:url(#<root>nej_editor.png?"
  },
  {
    "path": "src/ui/editor/editor.html",
    "chars": 671,
    "preview": "<textarea name='jst' id='#<seedIcmd>'>\n{list xlist as x}\n    <div class=\"zitm zbg ${'js-'|seed}\" data-command=\"${x.cmd}\""
  },
  {
    "path": "src/ui/editor/editor.js",
    "chars": 7357,
    "preview": "/*\n * ------------------------------------------\n * 富媒体编辑器基类封装实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netea"
  },
  {
    "path": "src/ui/editor/test/custom.test.html",
    "chars": 1080,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>custom测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"http"
  },
  {
    "path": "src/ui/editor/test/custom.test.js",
    "chars": 1983,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"custom\",{\n        setup:function(){\n            this._e = nej.e;\n          "
  },
  {
    "path": "src/ui/item/item.js",
    "chars": 4697,
    "preview": "/*\n * ------------------------------------------\n * 列表项控件基类实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease."
  },
  {
    "path": "src/ui/item/list.js",
    "chars": 2468,
    "preview": "/*\n * ------------------------------------------\n * 列表项基类实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.co"
  },
  {
    "path": "src/ui/layer/card.css",
    "chars": 47,
    "preview": ".#<uispace>{position:absolute;background:#fff;}"
  },
  {
    "path": "src/ui/layer/card.js",
    "chars": 11736,
    "preview": "/*\n * ------------------------------------------\n * 卡片控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.com"
  },
  {
    "path": "src/ui/layer/card.wrapper.js",
    "chars": 152,
    "preview": "// link to ui/layer/wrapper/card for compatible\n// use ui/layer/wrapper/card for new project\nNEJ.define(['./wrapper/card"
  },
  {
    "path": "src/ui/layer/demo/card.html",
    "chars": 2567,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>test window</title>\n    <meta charset=\"utf-8\" />\n    <style>\n        body,htm"
  },
  {
    "path": "src/ui/layer/demo/window.html",
    "chars": 2188,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>test window</title>\n    <meta charset=\"utf-8\" />\n    <style>\n        body,htm"
  },
  {
    "path": "src/ui/layer/layer.js",
    "chars": 6859,
    "preview": "/*\n * ------------------------------------------\n * 弹出层控件基类实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease."
  },
  {
    "path": "src/ui/layer/layer.wrapper.js",
    "chars": 156,
    "preview": "// link to ui/layer/wrapper/layer for compatible\n// use ui/layer/wrapper/layer for new project\nNEJ.define(['./wrapper/l"
  },
  {
    "path": "src/ui/layer/test/card.test.html",
    "chars": 758,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>card测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"http:/"
  },
  {
    "path": "src/ui/layer/test/card.test.js",
    "chars": 738,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"ui-card\");\n\n    var _  = NEJ.P,\n        _e = _('nej.e'),\n        _p = _('ne"
  },
  {
    "path": "src/ui/layer/test/layer.test.html",
    "chars": 657,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>layer测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"http:"
  },
  {
    "path": "src/ui/layer/test/layer.test.js",
    "chars": 536,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"layer\");\n    var _  = NEJ.P,\n        _e = _('nej.e'),\n        _p = _('nej.u"
  },
  {
    "path": "src/ui/layer/test/mylayer.js",
    "chars": 1541,
    "preview": "/*\n * ------------------------------------------\n * 卡片控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.com"
  },
  {
    "path": "src/ui/layer/test/mylayercard.js",
    "chars": 659,
    "preview": "var f = function(){\n    var _  = NEJ.P,\n        _f = NEJ.F,\n        _u = _('nej.u'),\n        _e = _('nej.e'),\n        _p"
  },
  {
    "path": "src/ui/layer/test/mylayerwrapper.js",
    "chars": 1372,
    "preview": "/*\n * ------------------------------------------\n * 弹出卡片封装基类实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease"
  },
  {
    "path": "src/ui/layer/test/mywindow.js",
    "chars": 882,
    "preview": "var f = function(){\n    var _  = NEJ.P,\n        _f = NEJ.F,\n        _u = _('nej.u'),\n        _e = _('nej.e'),\n        _p"
  },
  {
    "path": "src/ui/layer/test/window.test.html",
    "chars": 858,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>window测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"http"
  },
  {
    "path": "src/ui/layer/test/window.test.js",
    "chars": 626,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"ui-window\");\n\n    var _  = NEJ.P,\n        _e = _('nej.e'),\n        _p = _('"
  },
  {
    "path": "src/ui/layer/test/window.wrapper.test.html",
    "chars": 747,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>windowWarpper测试页</title>\n  \t\t<link rel=\"stylesheet\" hre"
  },
  {
    "path": "src/ui/layer/test/window.wrapper.test.js",
    "chars": 1026,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"ui-windowWarpper\");\n    \n    var _  = NEJ.P,\n        _e = _('nej.e'),\n     "
  },
  {
    "path": "src/ui/layer/window.css",
    "chars": 368,
    "preview": ".#<uispace>{position:absolute;z-index:1000;border:1px solid #aaa;background:#fff;}\n.#<uispace> .zbar{line-height:30px;ba"
  },
  {
    "path": "src/ui/layer/window.html",
    "chars": 135,
    "preview": "<div>\n  <div class=\"zbar\"><div class=\"zttl\">标题</div></div>\n  <div class=\"zcnt\"></div>\n  <span class=\"zcls\" title=\"关闭窗体\">"
  },
  {
    "path": "src/ui/layer/window.js",
    "chars": 9637,
    "preview": "/*\n * ------------------------------------------\n * 窗体控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.com"
  },
  {
    "path": "src/ui/layer/window.wrapper.js",
    "chars": 158,
    "preview": "// link to ui/layer/wrapper/window for compatible\n// use ui/layer/wrapper/window for new project\nNEJ.define(['./wrapper/"
  },
  {
    "path": "src/ui/layer/wrapper/card.js",
    "chars": 7483,
    "preview": "/*\n * ------------------------------------------\n * 弹出卡片封装基类实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease"
  },
  {
    "path": "src/ui/layer/wrapper/layer.js",
    "chars": 5350,
    "preview": "/*\n * ------------------------------------------\n * 弹出层封装基类实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease."
  },
  {
    "path": "src/ui/layer/wrapper/window.js",
    "chars": 2604,
    "preview": "/*\n * ------------------------------------------\n * 弹出窗体封装基类实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease"
  },
  {
    "path": "src/ui/lightbox/demo/lightbox.html",
    "chars": 1502,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>NEJ实例 - LightBox</title>\n    <meta charset=\"utf-8\" />\n    <style>\n        *{m"
  },
  {
    "path": "src/ui/lightbox/lightbox.css",
    "chars": 946,
    "preview": ".#<uispace>-mask{position:fixed;_position:absolute;z-index:5000;top:0;bottom:0;left:0;right:0;width:100%;height:100%;bac"
  },
  {
    "path": "src/ui/lightbox/lightbox.html",
    "chars": 339,
    "preview": "<div>\n  <span class=\"zbtn zcls\" data-action=\"close\" title=\"关闭\">×</span>\n  <span class=\"zbtn zbpt zprv j-zflag\" data-acti"
  },
  {
    "path": "src/ui/lightbox/lightbox.js",
    "chars": 8527,
    "preview": "/*\n * ------------------------------------------\n * 图片幻灯片控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease."
  },
  {
    "path": "src/ui/mask/mask.css",
    "chars": 148,
    "preview": ".#<uispace>{position:fixed;_position:absolute;z-index:100;top:0;bottom:0;left:0;right:0;width:100%;height:100%;backgroun"
  },
  {
    "path": "src/ui/mask/mask.js",
    "chars": 2576,
    "preview": "/*\n * ------------------------------------------\n * 盖层控件实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.com"
  },
  {
    "path": "src/ui/mask/test/mask.test.html",
    "chars": 758,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>mask测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"http:/"
  },
  {
    "path": "src/ui/mask/test/mask.test.js",
    "chars": 539,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"ui-mask\");\n\n    var _  = NEJ.P,\n        _e = _('nej.e'),\n        _p = _('ne"
  },
  {
    "path": "src/ui/pager/base.css",
    "chars": 250,
    "preview": ".#<uispace>{font-size:12px;line-height:160%;}\n.#<uispace> a{margin:0 2px;padding:2px 8px;color:#333;border:1px solid #aa"
  },
  {
    "path": "src/ui/pager/base.js",
    "chars": 8796,
    "preview": "/*\n * ------------------------------------------\n * 分页器控件基类封装实现文件\n * @version  1.0\n * @author   genify(caijf@corp.neteas"
  },
  {
    "path": "src/ui/pager/demo/pager.html",
    "chars": 784,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>demo for pager</title>\n    <meta charset=\"utf-8\" />\n    <style>\n        body{"
  },
  {
    "path": "src/ui/pager/pager.base.js",
    "chars": 128,
    "preview": "// link to ui/pager/base for compatible\n// use ui/pager/base for new project\nNEJ.define(['./base.js'],function(_t){retur"
  },
  {
    "path": "src/ui/pager/pager.js",
    "chars": 3129,
    "preview": "/*\n * ------------------------------------------\n * 分页器控件封装实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease."
  },
  {
    "path": "src/ui/pager/pager.simple.js",
    "chars": 134,
    "preview": "// link to ui/pager/simple for compatible\n// use ui/pager/simple for new project\nNEJ.define(['./simple.js'],function(_t)"
  },
  {
    "path": "src/ui/pager/simple.js",
    "chars": 3374,
    "preview": "/*\n * ------------------------------------------\n * 分页器控件封装实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease."
  },
  {
    "path": "src/ui/pager/test/pager.test.html",
    "chars": 1874,
    "preview": "<!DOCTYPE HTML>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\">\n\t\t<title>pager测试页</title>\n  \t\t<link rel=\"stylesheet\" href=\"http:"
  },
  {
    "path": "src/ui/pager/test/pager.test.js",
    "chars": 3525,
    "preview": "var f = function(){\n    //定义测试模块\n    module(\"pager\");\n    var p = NEJ.P('nej.ui'),\n        v = NEJ.P('nej.v'),\n\t\tt = NEJ"
  },
  {
    "path": "src/ui/portrait/complex.css",
    "chars": 1468,
    "preview": ".#<uispace>{width:410px;border:1px;font-size:12px;text-align:center;}\n.#<uispace>,.#<uispace> .zbrd{border-style:solid;b"
  },
  {
    "path": "src/ui/portrait/complex.html",
    "chars": 598,
    "preview": "<textarea name='ntp' id='#<mid>'>\n<div class=\"zbrd\">\n  <div class=\"zsdb\">\n    <a class=\"zbtn zpgr zpup j-flag\" href=\"#\" "
  },
  {
    "path": "src/ui/portrait/complex.js",
    "chars": 6682,
    "preview": "/*\n * ------------------------------------------\n * 复杂表情控件封装实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease"
  },
  {
    "path": "src/ui/portrait/demo/complex.html",
    "chars": 724,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>test complex portrait</title>\n    <meta charset=\"utf-8\" />\n    <script>\n     "
  },
  {
    "path": "src/ui/portrait/demo/simple.html",
    "chars": 721,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>test simple portrait</title>\n    <meta charset=\"utf-8\" />\n    <script>\n      "
  },
  {
    "path": "src/ui/portrait/portrait.complex.js",
    "chars": 143,
    "preview": "// link to ui/portrait/complex for compatible\n// use ui/portrait/complex for new project\nNEJ.define(['./complex.js'],fun"
  },
  {
    "path": "src/ui/portrait/portrait.css",
    "chars": 932,
    "preview": ".#<uispace>{width:310px;padding:5px;background:#e5e5e1;border:1px solid #888;}\n.#<uispace> .zlst{position:relative;heigh"
  },
  {
    "path": "src/ui/portrait/portrait.html",
    "chars": 399,
    "preview": "<textarea name='ntp' id='#<mid>'>\n<div>\n\t<div class=\"zlst j-flag\"></div>\n\t<div class=\"zpbx j-flag\"></div>\n</div>\n</texta"
  },
  {
    "path": "src/ui/portrait/portrait.js",
    "chars": 8122,
    "preview": "/*\n * ------------------------------------------\n * 表情控件封装实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease.c"
  },
  {
    "path": "src/ui/portrait/portrait.simple.js",
    "chars": 140,
    "preview": "// link to ui/portrait/simple for compatible\n// use ui/portrait/simple for new project\nNEJ.define(['./simple.js'],functi"
  },
  {
    "path": "src/ui/portrait/simple.js",
    "chars": 3751,
    "preview": "/*\n * ------------------------------------------\n * 简易表情控件封装实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease"
  },
  {
    "path": "src/ui/range/range.css",
    "chars": 159,
    "preview": ".#<uispace>{position:absolute;top:0;left:0;width:0;height:0;overflow:hidden;border:1px solid #0C32F6;background:#B8D4F0;"
  },
  {
    "path": "src/ui/range/range.js",
    "chars": 3543,
    "preview": "/*\n * ------------------------------------------\n * 范围选择控件封装实现文件\n * @version  1.0\n * @author   genify(caijf@corp.netease"
  },
  {
    "path": "src/ui/resizer/demo/resizer.html",
    "chars": 611,
    "preview": "<!DOCTYPE html>\n<html>\n  <head>\n    <title>resizer demo</title>\n    <meta charset=\"utf-8\" />\n    <style>\n        body,ht"
  }
]

// ... and 439 more files (download for full content)

About this extraction

This page contains the full source code of the genify/nej GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 639 files (2.4 MB), approximately 656.4k tokens, and a symbol index with 30 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!