├── source ├── en │ ├── index.ejs │ ├── example │ │ └── index.md │ └── practice │ │ └── index.md ├── example │ └── index.md ├── practice │ └── index.md ├── img │ ├── life-cycle.png │ └── life-cycle.pptx └── _posts │ ├── practice │ ├── question-and-answer.md │ ├── data-valid.md │ ├── data-invalid.md │ ├── traverse-object.md │ ├── child-to-parent.md │ ├── child-to-grandparent.md │ ├── array-deep-updates-trigger-view.md │ ├── dynamic-parent-child.md │ ├── can-we-use-dom.md │ ├── auto-camel.md │ └── parent-to-child.md │ ├── en │ ├── practice │ │ ├── question-and-answer.md │ │ ├── traverse-object.md │ │ ├── data-invalid.md │ │ ├── data-valid.md │ │ ├── child-to-parent.md │ │ ├── child-to-grandparent.md │ │ ├── dynamic-parent-child.md │ │ └── array-deep-updates-trigger-view.md │ └── tutorial │ │ ├── if.md │ │ ├── style.md │ │ ├── start.md │ │ ├── data-checking.md │ │ ├── form.md │ │ ├── for.md │ │ └── ssr.md │ └── tutorial │ ├── style.md │ ├── if.md │ ├── ssr.md │ ├── start.md │ ├── form.md │ ├── reverse.md │ ├── background.md │ ├── for.md │ ├── data-checking.md │ ├── setup.md │ └── event.md ├── themes └── san │ ├── .npmignore │ ├── source │ ├── img │ │ ├── b_api.ai │ │ ├── logo2.png │ │ ├── banner-md.png │ │ ├── favicon.ico │ │ ├── lowpoly.jpg │ │ ├── macbook.png │ │ ├── san-perf.png │ │ ├── search_32px_1197039_easyicon.net.ico │ │ ├── Search.svg │ │ ├── logo-colorful.svg │ │ ├── logo.svg │ │ ├── 2.svg │ │ ├── 1.svg │ │ ├── b_compass.svg │ │ ├── pen.svg │ │ ├── b_update.svg │ │ ├── b_router.svg │ │ ├── Shape.svg │ │ ├── b_trail.svg │ │ ├── github2.svg │ │ ├── search02.svg │ │ ├── 5.svg │ │ ├── github.svg │ │ ├── 8.svg │ │ ├── 3.svg │ │ ├── b_store.svg │ │ ├── 4.svg │ │ ├── 9.svg │ │ ├── b_api.svg │ │ ├── b_mater.svg │ │ ├── 7.svg │ │ ├── b_design.svg │ │ └── 6.svg │ ├── fonts │ │ ├── Brandon_med.otf │ │ ├── FontAwesome.otf │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ └── fontawesome-webfont.woff2 │ ├── js │ │ ├── data.json │ │ ├── compass.json │ │ ├── stickUp.min.js │ │ ├── script.js │ │ ├── update.json │ │ ├── layout_control.js │ │ ├── example.json │ │ ├── api.json │ │ └── dev.json │ └── css │ │ └── code.css │ ├── layout │ ├── post.ejs │ ├── _partial │ │ ├── post │ │ │ ├── tag.ejs │ │ │ ├── date.ejs │ │ │ ├── category.ejs │ │ │ ├── gallery.ejs │ │ │ ├── nav.ejs │ │ │ └── title.ejs │ │ ├── article.ejs │ │ ├── mobilenav.ejs │ │ ├── head.ejs │ │ └── header.ejs │ ├── layout.ejs │ ├── example.ejs │ └── practice.ejs │ ├── languages │ ├── no.yml │ ├── ru.yml │ ├── nl.yml │ ├── fr.yml │ ├── default.yml │ └── en.yml │ └── _config.yml ├── scaffolds ├── draft.md ├── page.md └── post.md ├── .npmignore ├── .gitignore ├── scripts └── helpers.js ├── circle.yml ├── package.json ├── README.md └── _config.yml /source/en/index.ejs: -------------------------------------------------------------------------------- 1 | layout: index 2 | --- -------------------------------------------------------------------------------- /source/en/example/index.md: -------------------------------------------------------------------------------- 1 | layout: example 2 | --- -------------------------------------------------------------------------------- /source/en/practice/index.md: -------------------------------------------------------------------------------- 1 | layout: practice 2 | --- -------------------------------------------------------------------------------- /source/example/index.md: -------------------------------------------------------------------------------- 1 | layout: example 2 | --- 3 | -------------------------------------------------------------------------------- /source/practice/index.md: -------------------------------------------------------------------------------- 1 | layout: practice 2 | --- 3 | -------------------------------------------------------------------------------- /themes/san/.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | tmp -------------------------------------------------------------------------------- /scaffolds/draft.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | tags: 4 | --- 5 | -------------------------------------------------------------------------------- /scaffolds/page.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | date: {{ date }} 4 | --- 5 | -------------------------------------------------------------------------------- /scaffolds/post.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: {{ title }} 3 | date: {{ date }} 4 | tags: 5 | --- 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | db.json 4 | *.log 5 | node_modules/ 6 | public/ 7 | .deploy*/ -------------------------------------------------------------------------------- /source/img/life-cycle.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/source/img/life-cycle.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | Thumbs.db 3 | db.json 4 | *.log 5 | .idea 6 | node_modules/ 7 | public/ 8 | .deploy*/ -------------------------------------------------------------------------------- /source/img/life-cycle.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/source/img/life-cycle.pptx -------------------------------------------------------------------------------- /themes/san/source/img/b_api.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/img/b_api.ai -------------------------------------------------------------------------------- /themes/san/source/img/logo2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/img/logo2.png -------------------------------------------------------------------------------- /themes/san/source/img/banner-md.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/img/banner-md.png -------------------------------------------------------------------------------- /themes/san/source/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/img/favicon.ico -------------------------------------------------------------------------------- /themes/san/source/img/lowpoly.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/img/lowpoly.jpg -------------------------------------------------------------------------------- /themes/san/source/img/macbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/img/macbook.png -------------------------------------------------------------------------------- /themes/san/source/img/san-perf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/img/san-perf.png -------------------------------------------------------------------------------- /themes/san/source/fonts/Brandon_med.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/fonts/Brandon_med.otf -------------------------------------------------------------------------------- /themes/san/source/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /themes/san/source/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /themes/san/source/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /themes/san/source/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /themes/san/source/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /themes/san/source/img/search_32px_1197039_easyicon.net.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/andycall/san-website/master/themes/san/source/img/search_32px_1197039_easyicon.net.ico -------------------------------------------------------------------------------- /themes/san/layout/post.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 | <%- partial('_partial/article', {post: page, index: false}) %> 4 |
-------------------------------------------------------------------------------- /themes/san/layout/_partial/post/tag.ejs: -------------------------------------------------------------------------------- 1 | <% if (post.tags && post.tags.length){ %> 2 | <%- list_tags(post.tags, { 3 | show_count: false, 4 | class: 'article-tag' 5 | }) %> 6 | <% } %> -------------------------------------------------------------------------------- /themes/san/layout/_partial/post/date.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /scripts/helpers.js: -------------------------------------------------------------------------------- 1 | hexo.extend.helper.register('url_for_lang', function(path, lang) { 2 | lang = lang == null ? this.page.lang : lang; 3 | 4 | if (lang) { 5 | path = lang + '/' + path; 6 | } 7 | 8 | return this.url_for(path); 9 | }); -------------------------------------------------------------------------------- /circle.yml: -------------------------------------------------------------------------------- 1 | machine: 2 | node: 3 | version: 6 4 | 5 | deployment: 6 | production: 7 | branch: master 8 | commands: 9 | - git config --global user.name erik 10 | - git config --global user.email erik168@163.com 11 | - npm run build 12 | -------------------------------------------------------------------------------- /themes/san/layout/_partial/post/category.ejs: -------------------------------------------------------------------------------- 1 | <% if (post.categories && post.categories.length){ %> 2 |
3 | <%- list_categories(post.categories, { 4 | show_count: false, 5 | class: 'article-category', 6 | style: 'none', 7 | separator: '►' 8 | }) %> 9 |
10 | <% } %> -------------------------------------------------------------------------------- /themes/san/layout/layout.ejs: -------------------------------------------------------------------------------- 1 | <%- partial('_partial/head') %> 2 | 3 |
4 | <%- partial('_partial/header') %> 5 | <%- body %> 6 | <%- partial('_partial/mobilenav') %> 7 |
8 | 9 | 10 | -------------------------------------------------------------------------------- /themes/san/languages/no.yml: -------------------------------------------------------------------------------- 1 | categories: Kategorier 2 | search: Søk 3 | tags: Tags 4 | tagcloud: Tag Cloud 5 | tweets: Tweets 6 | prev: Forrige 7 | next: Neste 8 | comment: Kommentarer 9 | archive_a: Arkiv 10 | archive_b: "Arkiv: %s" 11 | page: Side %d 12 | recent_posts: Siste innlegg 13 | newer: Newer 14 | older: Older 15 | share: Share 16 | powered_by: Powered by 17 | rss_feed: RSS Feed 18 | category: Category 19 | tag: Tag -------------------------------------------------------------------------------- /themes/san/languages/ru.yml: -------------------------------------------------------------------------------- 1 | categories: Категории 2 | search: Поиск 3 | tags: Метки 4 | tagcloud: Облако меток 5 | tweets: Твиты 6 | prev: Назад 7 | next: Вперед 8 | comment: Комментарии 9 | archive_a: Архив 10 | archive_b: "Архив: %s" 11 | page: Страница %d 12 | recent_posts: Недавние записи 13 | newer: Следующий 14 | older: Предыдущий 15 | share: Поделиться 16 | powered_by: Создано с помощью 17 | rss_feed: RSS-каналы 18 | category: Категория 19 | tag: Метка -------------------------------------------------------------------------------- /themes/san/layout/_partial/post/gallery.ejs: -------------------------------------------------------------------------------- 1 | <% if (post.photos && post.photos.length){ %> 2 |
3 |
4 | <% post.photos.forEach(function(photo, i){ %> 5 | 6 | 7 | 8 | <% }) %> 9 |
10 |
11 | <% } %> -------------------------------------------------------------------------------- /themes/san/languages/nl.yml: -------------------------------------------------------------------------------- 1 | 2 | categories: Categorieën 3 | search: Zoeken 4 | tags: Labels 5 | tagcloud: Tag Cloud 6 | tweets: Tweets 7 | prev: Vorige 8 | next: Volgende 9 | comment: Commentaren 10 | archive_a: Archieven 11 | archive_b: "Archieven: %s" 12 | page: Pagina %d 13 | recent_posts: Recente berichten 14 | newer: Nieuwer 15 | older: Ouder 16 | share: Delen 17 | powered_by: Powered by 18 | rss_feed: RSS Feed 19 | category: Categorie 20 | tag: Label 21 | -------------------------------------------------------------------------------- /themes/san/languages/fr.yml: -------------------------------------------------------------------------------- 1 | categories: Catégories 2 | search: Rechercher 3 | tags: Mot-clés 4 | tagcloud: Nuage de mot-clés 5 | tweets: Tweets 6 | prev: Précédent 7 | next: Suivant 8 | comment: Commentaires 9 | archive_a: Archives 10 | archive_b: "Archives: %s" 11 | page: Page %d 12 | recent_posts: Articles récents 13 | newer: Récent 14 | older: Ancien 15 | share: Partager 16 | powered_by: Propulsé by 17 | rss_feed: Flux RSS 18 | category: Catégorie 19 | tag: Mot-clé 20 | -------------------------------------------------------------------------------- /source/_posts/practice/question-and-answer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Q&A集锦 3 | categories: 4 | - practice 5 | --- 6 | 7 | 这里是一些常见问题的解答,有任何问题随时给我们提 issue,我们会持续更新这里,方便您尽快发现问题的答案 8 | 9 | ### 实践问题 10 | 11 | 1.`san-for`和`san-ref`可以一起使用吗? 12 | A:可以的,使用方式为https://github.com/baidu/san/blob/master/test/component.spec.js#L1547 13 | 14 | ### 其它问题 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "san-site", 3 | "version": "1.0.0", 4 | "hexo": { 5 | "version": "3.7.1" 6 | }, 7 | "scripts": { 8 | "start": "npx hexo s", 9 | "deploy": "npx hexo clean && npx hexo g && npx hexo deploy" 10 | }, 11 | "dependencies": { 12 | "hexo": "^3.7.1", 13 | "hexo-deployer-git": "^0.3.1", 14 | "hexo-generator-archive": "^0.1.4", 15 | "hexo-generator-category": "^0.1.3", 16 | "hexo-generator-index": "^0.2.0", 17 | "hexo-generator-tag": "^0.2.0", 18 | "hexo-renderer-ejs": "^0.3.0", 19 | "hexo-renderer-marked": "^0.3.0", 20 | "hexo-server": "^0.2.0" 21 | } 22 | } -------------------------------------------------------------------------------- /source/_posts/en/practice/question-and-answer.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: FAQ 3 | categories: 4 | - practice 5 | --- 6 | 7 | Here are some answers to frequently asked questions. If you have any questions, please feel free to ask us an issue. We will continue to update them here so that you can find the answers to your questions as soon as possible. 8 | 9 | ### Practice issues 10 | 11 | 1. Can `san-for` and `san-ref` be used together? 12 | A:Yes. More details refer to https://github.com/baidu/san/blob/master/test/component.spec.js#L1547 13 | 14 | ### Other issues 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /themes/san/_config.yml: -------------------------------------------------------------------------------- 1 | # Header 2 | menu: 3 | Home: / 4 | Archives: /archives 5 | rss: /atom.xml 6 | 7 | # i18n 8 | language: 9 | - zh-cn 10 | - en 11 | 12 | # Content 13 | excerpt_link: Read More 14 | fancybox: true 15 | 16 | # Sidebar 17 | sidebar: right 18 | widgets: 19 | - category 20 | - tag 21 | - tagcloud 22 | - archive 23 | - recent_posts 24 | 25 | # display widgets at the bottom of index pages (pagination == 2) 26 | index_widgets: 27 | # - category 28 | # - tagcloud 29 | # - archive 30 | 31 | # widget behavior 32 | archive_type: 'monthly' 33 | show_count: false 34 | 35 | # Miscellaneous 36 | google_analytics: 37 | favicon: /favicon.png 38 | twitter: 39 | google_plus: 40 | fb_admins: 41 | fb_app_id: 42 | -------------------------------------------------------------------------------- /source/_posts/practice/data-valid.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 什么东西可以保存在data里? 3 | categories: 4 | - practice 5 | --- 6 | 7 | 8 | 在 San 的文档中写道:" San 是一个 MVVM 的组件框架,通过 San 的视图引擎能够让用户只用操作数据,视图自动更新"。这里要说的 data 指的就是用户操作的"数据"。data 里应该存与视图相关的数据状态。 9 | 10 | #### data 中保存的数据 11 | 12 | 对于一个组件,data 数据的来源可分为如下两种: 13 | 1、组件自身定义的数据; 14 | 2、从父组件中传入的数据; 15 | 3、computed 中定义的数据; 16 | 以上三种,我们都可以通过`this.data.get()`方法获取。 17 | 18 | 组件自身定义的数据(状态)保存在该组件的 data 中,可以对其进行修改从而影响当前组件以及子组件的视图。从父组件传入的数据,可以从 data 中获取,但通常我们只是使用这个数据,如果要更改从父组件传入的数据,虽然可以直接在组件内更改,但通常的做法是到该数据初始化的地方去更改。 19 | 20 | #### data 数据应该是纯数据 21 | 22 | data 数据可以是字符串、数值、 数组、原生对象这样的纯数据。对于正则表达式,纯函数这样的,如果是在组件自身使用,则需要外部引入,或者作为组件的方法。 但如果要传递给子组件使用,则可以存放在 data 中。 例如,在表单组件中,我们可能会在业务层自定义验证方法,传入子组件使用。 23 | 24 | -------------------------------------------------------------------------------- /themes/san/layout/_partial/post/nav.ejs: -------------------------------------------------------------------------------- 1 | <% if (post.prev || post.next){ %> 2 | 22 | <% } %> -------------------------------------------------------------------------------- /themes/san/layout/_partial/post/title.ejs: -------------------------------------------------------------------------------- 1 |
2 | <% if (post.link){ %> 3 |

4 | 5 |

6 | <% } else if (post.title){ %> 7 | <% if (index){ %> 8 |

9 | <%= post.title %> 10 |

11 | <% } else { %> 12 |

13 | <%= post.title %> 14 |

15 | <% } %> 16 | <% } %> 17 | <%= __('article.edit') %> 18 |
-------------------------------------------------------------------------------- /source/_posts/practice/data-invalid.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 什么东西不要保存在 data 里? 3 | categories: 4 | - practice 5 | --- 6 | 7 | 我们知道,data 里应该存与视图相关的数据状态。我们在下面列举了一些不当的使用场景,这些场景是我们不止发现过一次的。 8 | 9 | #### 函数 10 | 11 | 不要把函数作为数据存放。函数应该是独立的,或者作为组件方法存在。 12 | 13 | ```javascript 14 | // bad 15 | this.data.set('camel2kebab', function (source) { 16 | return source.replace(/[A-Z]/g, function (match) { 17 | return '-' + match.toLowerCase(); 18 | }); 19 | }); 20 | ``` 21 | 22 | #### DOM 对象 23 | 24 | 这个应该不用解释吧。 25 | 26 | ```javascript 27 | // bad 28 | this.data.set('sideEl', document.querySelector('sidebar')); 29 | ``` 30 | 31 | #### 组件等复杂对象 32 | 33 | 不要使用数据来做动态子组件的管理。动态子组件对象可以直接存在组件的成员中。 34 | 35 | ```javascript 36 | // bad 37 | var layer = new Layer(); 38 | layer.attach(document.body); 39 | this.data.set('layer', layer); 40 | 41 | // good 42 | var layer = new Layer(); 43 | layer.attach(document.body); 44 | this.layer = layer; 45 | ``` 46 | -------------------------------------------------------------------------------- /themes/san/layout/_partial/article.ejs: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%- partial('post/gallery') %> 4 | <% if (post.link || post.title){ %> 5 | <%- partial('post/title', {class_name: 'article-title'}) %> 6 | <% } %> 7 |
8 | <% if (post.excerpt && index){ %> 9 | <%- post.excerpt %> 10 | <% if (theme.excerpt_link){ %> 11 |

12 | <%= theme.excerpt_link %> 13 |

14 | <% } %> 15 | <% } else { %> 16 | <%- post.content %> 17 | <% } %> 18 |
19 |
20 |
21 | 22 | 26 | -------------------------------------------------------------------------------- /themes/san/source/img/Search.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/san/layout/_partial/mobilenav.ejs: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 |
7 |
8 | 17 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /themes/san/source/img/logo-colorful.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /source/_posts/practice/traverse-object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 如何遍历一个对象? 3 | categories: 4 | - practice 5 | --- 6 | 7 | 在San中已经提供了 san-for 指令(可以简写为 s-for )将 Array 渲染为页面中的列表,那么对于 Object 想要进行遍历并渲染应当怎么做呢?由于 San 的指令并不直接支持 Object 的遍历,因此可以使用计算属性进行对象的遍历 8 | 9 | #### 使用 10 | 11 | ```javascript 12 | class MyComponent extends San.component { 13 | static computed = { 14 | list() { 15 | let myObject = this.data.get('myObject'); 16 | return Object.keys(myObject).map(item => { 17 | return { 18 | key: item, 19 | value: myObject[item] 20 | } 21 | }); 22 | } 23 | }; 24 | } 25 | ``` 26 | 27 | #### 示例 28 | 29 |

See the Pen san-traverse-object by liuchaofan (@asd123freedom) on CodePen.

30 | 31 | -------------------------------------------------------------------------------- /themes/san/source/img/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 3-logo方案3-0 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /source/_posts/en/practice/traverse-object.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Object Traversal 3 | categories: 4 | - practice 5 | --- 6 | 7 | San provides san-for directive (abbreviated as s-for) to render a list from Array data. Then how to traverse and render Object data? Since san does not provide directives for Object traversal, you can make use of the **computed property**. 8 | 9 | #### Usage 10 | 11 | ```javascript 12 | class MyComponent extends San.component { 13 | static computed = { 14 | list() { 15 | let myObject = this.data.get('myObject'); 16 | return Object.keys(myObject).map(item => { 17 | return { 18 | key: item, 19 | value: myObject[item] 20 | } 21 | }); 22 | } 23 | }; 24 | } 25 | ``` 26 | 27 | #### Demo 28 | 29 |

See the Pen san-traverse-object by liuchaofan (@asd123freedom) on CodePen.

30 | 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | San Website 2 | ------------ 3 | 4 | ### prepare 5 | 6 | ``` 7 | $ npm i 8 | ``` 9 | 10 | ### preview 11 | 12 | ``` 13 | # npm start 14 | hexo s 15 | ``` 16 | 17 | 18 | ### deploy 19 | 20 | ``` 21 | # npm run deploy 22 | $ hexo deploy 23 | ``` 24 | 25 | 26 | 27 | 28 | 29 | 给 San 文档做贡献 30 | ------------ 31 | 32 | 33 | ### San 是一个传统的 MVVM 组件框架,官网地址 https://baidu.github.io/san/ 34 | 35 | 如果你在使用 San 过程中遇到任何问题,请通过 github: https://github.com/baidu/san 给我们提 issue; 36 | 如果你在看 San 文档的过程中发现任何问题,可以通过文档 github: https://github.com/baidu/san-website 给我们提 issue,或者在相应位置修改后发起 PR; 37 | 当然我们非常欢迎您在实践 San 框架过程中,有任何实践经验或总结文档,可以通过 PR 的方式提交到文档 github: https://github.com/baidu/san-website, 经过我们 review 后的文档会合入 San 的官方文档中。 38 | 39 | 40 | ### San 文档 PR 规范 41 | 42 | 1. **提交前的 fork 同步更新操作**:每次 PR 前请进行 fork 同步更新操作,避免产生冲突。 43 | 44 | 2. **文档内容**: 必须包含对实践问题的原理分析总结,包含实际的 demo,demo 的编写请使用 codepen,最后将其嵌入到文档中,具体详情及嵌入方式请见例子: 45 | https://baidu.github.io/san/practice/traverse-object/. 46 | 3. **实践类文档项目路径**: 47 | 48 | - [ ] 添加文档可以往这里发 PR 49 | 50 | https://github.com/baidu/san-website/tree/master/source/_posts/practice 51 | 52 | - [ ] 链接是手工加 53 | 54 | https://github.com/baidu/san-website/blob/master/themes/san/layout/practice.ejs 55 | 56 | 4. ** PR 标题与内容**:PR 标题和内容,请对文档进行详细说明,并提供文档的最终截图。 57 | 58 | 有任何文档问题可以给我们提 issue -------------------------------------------------------------------------------- /source/_posts/en/practice/data-invalid.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: What Content is Not Suitable for the Data? 3 | categories: 4 | - practice 5 | --- 6 | 7 | As introduced before, the contents of data are expected to be view related. In this article we give a list of inproppriate uses of the data. 8 | 9 | #### Functions 10 | 11 | Functions are not suitable to be stored in the data. Functions are expected to be separated from the data, and can be imported from other source files or implemented as component methods. 12 | 13 | ```javascript 14 | // bad 15 | this.data.set('camel2kebab', function (source) { 16 | return source.replace(/[A-Z]/g, function (match) { 17 | return '-' + match.toLowerCase(); 18 | }); 19 | }); 20 | ``` 21 | 22 | #### DOM Objects 23 | 24 | ```javascript 25 | // bad 26 | this.data.set('sideEl', document.querySelector('sidebar')); 27 | ``` 28 | 29 | #### Complex Objects 30 | 31 | Complex objects like components are not suitable to be stored in the data. 32 | For example, using data to store dynamic child components is not recommended, use member properties instead. 33 | 34 | ```javascript 35 | // bad 36 | var layer = new Layer(); 37 | layer.attach(document.body); 38 | this.data.set('layer', layer); 39 | 40 | // good 41 | var layer = new Layer(); 42 | layer.attach(document.body); 43 | this.layer = layer; 44 | ``` 45 | -------------------------------------------------------------------------------- /themes/san/source/img/2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /source/_posts/practice/child-to-parent.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 子组件如何通知父组件? 3 | categories: 4 | - practice 5 | --- 6 | 7 | San的组件体系提供了事件功能,子组件可以通过调用组件的[fire](/san/doc/api/#fire)方法派发一个自定义事件,父组件在视图模板中通过on-事件名的方式或通过子组件实例的on方法就可以监听子组件派发的自定义事件,实现子组件到父组件的通信。 8 | 9 | #### 使用 10 | ```javascript 11 | var childComponent = san.defineComponent({ 12 |   template: ` 13 |
14 |           15 |
16 | `, 17 | 18 | onClick: function () { 19 |        // 向父组件派发一个child-change事件 20 |       this.fire('child-change', 'from child'); 21 | } 22 | }); 23 | 24 | var parentComponent = san.defineComponent({ 25 | components: { 26 | 'my-child': 'childComponent' 27 | }, 28 | 29 | template: ` 30 |
31 |         32 |
33 | `, 34 | 35 | changeHandler: function (val) { 36 |        // 事件处理 37 | } 38 | 39 | }); 40 | ``` 41 | 说明: 我们知道使用「双向绑定」可以将子组件内部的数据变化同步给父组件,但除了类表单组件外,其它情况不建议使用「双向绑定」的方式来达到通知父组件的目的。 42 | 43 | #### 示例 44 |

See the Pen child-to-parent by funa (@naatgit) on CodePen.

45 | 46 | -------------------------------------------------------------------------------- /source/_posts/en/practice/data-valid.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: What Content is Suitable for the Data? 3 | categories: 4 | - practice 5 | --- 6 | 7 | 8 | The introduction of san says: "San is a MVVM component framework. With the help of san view engine, one can focus on the data only and views will be updated automatically." We're going to discuss the "data" to be manipulated. The contents of the "data" are expected to be view related states only. 9 | 10 | #### Sources of the Data 11 | 12 | For a component, the source of the data can be: 13 | 14 | 1. Data defined by the component itself; 15 | 2. Data passed in from the parent component; 16 | 3. Data defined by the computed property. 17 | 18 | All of the above data can be accessed by `this.data.get()` method. 19 | 20 | The data defined by the component itself can be modified directly to update the view of the component and it's descendant components. The data passed in from the parent component are usually for read purposes only. Though we can also change these data in child components, the better practice is to change it in the component where the data is initially created. 21 | 22 | #### Contents of the Data 23 | 24 | The entries in the data can be of primitive types like String, Number, Array, plain Objects. Other objects like regular expressions and functions are not expected to be part of the data and can be imported or implemented inside the component instead. There's an exception when we need to pass objects to the child components, such as passing custom form validation functions to the child components. 25 | 26 | -------------------------------------------------------------------------------- /themes/san/source/img/1.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /themes/san/source/img/b_compass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b_compass 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /themes/san/source/img/pen.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /themes/san/source/js/data.json: -------------------------------------------------------------------------------- 1 | {"v":"4.12.2","fr":60,"ip":0,"op":240,"w":128,"h":128,"nm":"合成 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“compass”轮廓 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64,64,0],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[43,85],[68,70],[58,60]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-13.976,23.735],[-0.454,-0.751],[13.976,-23.735],[0.454,0.751]],"o":[[24.527,-13.77],[0.304,0.503],[-24.527,13.77],[-0.304,-0.503]],"v":[[55.518,55.586],[92.99,36.058],[72.482,72.414],[35.01,91.942]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"“compass”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64,64,0],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[28.719,0],[0,28.719],[-28.719,0],[0,-28.719]],"o":[[-28.719,0],[0,-28.719],[28.719,0],[0,28.719]],"v":[[64,116],[12,64],[64,12],[116,64]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0}]} -------------------------------------------------------------------------------- /themes/san/source/img/b_update.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b_update 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /source/_posts/en/practice/child-to-parent.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Child-to-Parent Messaging 3 | categories: 4 | - practice 5 | --- 6 | 7 | San components provide event mechanism. By calling [fire](/san/doc/api/#fire) method child component can dispatch a custom event, which will be received by the parent component via the on-"event name" directive or **on** method on the child component instance. That is child-to-parent component messaging. 8 | 9 | #### Usage 10 | ```javascript 11 | var childComponent = san.defineComponent({ 12 |   template: ` 13 |
14 |           15 |
16 | `, 17 | 18 | onClick: function () { 19 |        // Dispatch a child-change event 20 |       this.fire('child-change', 'from child'); 21 | } 22 | }); 23 | 24 | var parentComponent = san.defineComponent({ 25 | components: { 26 | 'my-child': 'childComponent' 27 | }, 28 | 29 | template: ` 30 |
31 |         32 |
33 | `, 34 | 35 | changeHandler: function (val) { 36 |        // event handling 37 | } 38 | 39 | }); 40 | ``` 41 | Note: Two-way data binding can also achieve child-to-parent messaging. But it's not recommended to notify the parent component using two-way data binding other than use cases like form components. 42 | 43 | #### Demo 44 |

See the Pen child-to-parent by funa (@naatgit) on CodePen.

45 | 46 | -------------------------------------------------------------------------------- /source/_posts/practice/child-to-grandparent.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 子组件与更高层组件如何通信? 3 | categories: 4 | - practice 5 | --- 6 | 7 | #### 使用 8 | 9 | 子组件通过**dispatch**方法向组件树上层派发消息。 10 | 11 | 12 | ```javascript 13 | class Son extends san.Component { 14 | static template = ` 15 |
16 | 17 |
18 | `; 19 | 20 | onClick() { 21 | const value = this.data.get('value'); 22 | // 向组件树的上层派发消息 23 | this.dispatch('son-clicked', value); 24 | } 25 | }; 26 | ``` 27 | 28 | 消息将沿着组件树向上传递,直到遇到第一个处理该消息的组件,则停止。通过 **messages** 可以声明组件要处理的消息。**messages** 是一个对象,key 是消息名称,value 是消息处理的函数,接收一个包含 target(派发消息的组件) 和 value(消息的值) 的参数对象。 29 | 30 | ```javascript 31 | class GrandParent extends san.Component { 32 | static template = '
'; 33 | 34 | // 声明组件要处理的消息 35 | static messages = { 36 | 'son-clicked': function (arg) { 37 | // arg.target 可以拿到派发消息的组件 38 | // arg.value 可以拿到派发消息的值 39 | this.data.set('value', arg.value); 40 | 41 | } 42 | } 43 | }; 44 | ``` 45 | 46 | #### 示例 47 | 48 |

See the Pen higher-communication by Swan (@jiangjiu8357) on CodePen.

49 | 50 | -------------------------------------------------------------------------------- /source/_posts/tutorial/style.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 样式 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 样式处理是编写视图模板时常见的场景,涉及到的 attribute 有 class 和 style,它们的处理方式和其他元素 attribute 有一些区别。本文专门描述样式处理上常见的场景。 8 | 9 | 在开始前,先强调一下:San 并没有为 class 和 style 处理提供特殊的绑定语法,他们的处理与其它 attribute 方式一样。 10 | 11 | class 12 | ------ 13 | 14 | 我们可能会设计一些用于表示状态的 class,这些 class 是否应该被添加到元素上,取决于某些数据的值。一个简单的场景是下拉列表的收起和展开状态切换。 15 | 16 | ```html 17 | 18 |
19 | 20 | 21 |
22 | ``` 23 | 24 | ```javascript 25 | // Component 26 | san.defineComponent({ 27 | toggle: function () { 28 | var isHidden = this.data.get('isHidden'); 29 | this.data.set('isHidden', !isHidden); 30 | } 31 | }); 32 | ``` 33 | 34 | 上面的例子中,isHidden 数据为真时,ul 具有 list-hidden 的 class,为假时不具有。 35 | 36 | San 在设计时,希望视图模板开发者像写正常的 attribute 一样编写 class 与 style,所以没有提供特殊的绑定语法。通过三元运算符的支持可以处理这样的场景。 37 | 38 | 下面例子是一个根据状态不同,切换不同 class 的场景。 39 | 40 | 41 | ```html 42 | 43 | ``` 44 | 45 | style 46 | ----- 47 | 48 | 对 style 的处理通常没有 class 那么复杂。我们很少会把样式信息写在数据中,但有时我们期望用户能定制一些界面样式,这个时候样式可能来源于数据。 49 | 50 | ```html 51 | 59 | ``` 60 | 61 | 此时需要警惕的是,数据可能并不存在,导致你设置的 style 并不是一个合法的样式。如果你不能保证数据一定有值,需要把样式名包含在插值中。 62 | 63 | ```html 64 | 72 | ``` 73 | -------------------------------------------------------------------------------- /_config.yml: -------------------------------------------------------------------------------- 1 | # Hexo Configuration 2 | ## Docs: https://hexo.io/docs/configuration.html 3 | ## Source: https://github.com/hexojs/hexo/ 4 | 5 | # Site 6 | title: San 7 | subtitle: 8 | description: A Flexible JavaScript Component Framework 9 | author: errorrik 10 | language: 11 | timezone: 12 | 13 | # URL 14 | ## If your site is put in a subdirectory, set url as 'http://yoursite.com/child' and root as '/child/' 15 | url: http://baidu.github.io 16 | root: /san/ 17 | permalink: :title/ 18 | permalink_defaults: 19 | 20 | # Directory 21 | source_dir: source 22 | public_dir: public 23 | tag_dir: tags 24 | archive_dir: archives 25 | category_dir: categories 26 | code_dir: downloads/code 27 | i18n_dir: :lang 28 | skip_render: 29 | 30 | # Writing 31 | new_post_name: :title.md # File name of new posts 32 | default_layout: post 33 | titlecase: false # Transform title into titlecase 34 | external_link: true # Open external links in new tab 35 | filename_case: 0 36 | render_drafts: false 37 | post_asset_folder: false 38 | relative_link: false 39 | future: true 40 | highlight: 41 | enable: true 42 | line_number: true 43 | auto_detect: false 44 | tab_replace: 45 | 46 | # Category & Tag 47 | default_category: doc 48 | category_map: 49 | tag_map: 50 | 51 | # Date / Time format 52 | ## Hexo uses Moment.js to parse and display date 53 | ## You can customize the date format as defined in 54 | ## http://momentjs.com/docs/#/displaying/format/ 55 | date_format: YYYY-MM-DD 56 | time_format: HH:mm:ss 57 | 58 | # Pagination 59 | ## Set per_page to 0 to disable pagination 60 | per_page: 10 61 | pagination_dir: page 62 | 63 | # Extensions 64 | ## Plugins: https://hexo.io/plugins/ 65 | ## Themes: https://hexo.io/themes/ 66 | theme: san 67 | 68 | # Deployment 69 | ## Docs: https://hexo.io/docs/deployment.html 70 | deploy: 71 | type: git 72 | repo: git@github.com:baidu/san.git 73 | branch: gh-pages 74 | message: deploy san website 75 | -------------------------------------------------------------------------------- /themes/san/source/js/compass.json: -------------------------------------------------------------------------------- 1 | {"v":"4.12.2","fr":60,"ip":0,"op":60,"w":128,"h":128,"nm":"合成 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“compass”轮廓 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0],"y":[1]},"o":{"x":[0.4],"y":[0]},"n":["0_1_0p4_0"],"t":0,"s":[0],"e":[96]},{"i":{"x":[0.6],"y":[1]},"o":{"x":[1],"y":[0]},"n":["0p6_1_1_0"],"t":30,"s":[96],"e":[0]},{"t":60}],"ix":10},"p":{"a":0,"k":[64,64,0],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0]],"v":[[43,85],[68,70],[58,60]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-13.976,23.735],[-0.454,-0.751],[13.976,-23.735],[0.454,0.751]],"o":[[24.527,-13.77],[0.304,0.503],[-24.527,13.77],[-0.304,-0.503]],"v":[[55.518,55.586],[92.99,36.058],[72.482,72.414],[35.01,91.942]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"“compass”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[64,64,0],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[28.719,0],[0,28.719],[-28.719,0],[0,-28.719]],"o":[[-28.719,0],[0,-28.719],[28.719,0],[0,28.719]],"v":[[64,116],[12,64],[64,12],[116,64]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0}]} -------------------------------------------------------------------------------- /themes/san/source/img/b_router.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b_router 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /source/_posts/tutorial/if.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 条件 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 8 | s-if 9 | ------ 10 | 11 | 通过 **s-if** 指令,我们可以为元素指定条件。当条件成立时元素存在,当条件不成立时元素不存在。 12 | 13 | `提示`:当不满足条件时,San 会将元素从当前页面中移除,而不是隐藏。 14 | 15 | ```html 16 | Hello San! 17 | ``` 18 | 19 | **s-if** 指令的值可以是任何类型的[表达式](../template/#表达式)。 20 | 21 | ```html 22 | Hello San! 23 | ``` 24 | 25 | `提示`:San 的条件判断不是严格的 === false。所以,一切 JavaScript 的假值都会认为条件不成立:0、空字符串、null、undefined、NaN等。 26 | 27 | s-elif 28 | ------ 29 | 30 | `> 3.2.3` 31 | 32 | **s-elif** 指令可以给 **s-if** 增加一个额外条件分支块。**s-elif** 指令的值可以是任何类型的[表达式](../template/#表达式)。 33 | 34 | ```html 35 | Active 36 | Pending 37 | ``` 38 | 39 | `提示`:**s-elif** 指令元素必须跟在 **s-if** 或 **s-elif** 指令元素后,否则将抛出 **elif not match if** 的异常。 40 | 41 | 42 | s-else-if 43 | ------ 44 | 45 | `> 3.5.6` 46 | 47 | **s-else-if** 指令是 **s-elif** 指令的别名,效果相同。 48 | 49 | ```html 50 | Active 51 | Pending 52 | ``` 53 | 54 | 55 | s-else 56 | ------ 57 | 58 | **s-else** 指令可以给 **s-if** 增加一个不满足条件的分支块。**s-else** 指令没有值。 59 | 60 | ```html 61 | Hello! 62 | Offline 63 | ``` 64 | 65 | `提示`:**s-else** 指令元素必须跟在 **s-if** 或 **s-elif** 指令元素后,否则将抛出 **else not match if** 的异常。 66 | 67 | 68 | 虚拟元素 69 | ------ 70 | 71 | 在 san 中,template 元素在渲染时不会包含自身,只会渲染其内容。对 template 元素应用 if 指令能够让多个元素同时根据条件渲染视图,可以省掉一层不必要的父元素。 72 | 73 | ```html 74 |
75 | 79 | 83 | 87 | 91 |
92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /themes/san/layout/_partial/head.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <% 9 | var title = page.title; 10 | 11 | if (is_archive()){ 12 | title = __('archive_a'); 13 | 14 | if (is_month()){ 15 | title += ': ' + page.year + '/' + page.month; 16 | } else if (is_year()){ 17 | title += ': ' + page.year; 18 | } 19 | } else if (is_category()){ 20 | title = __('category') + ': ' + page.category; 21 | } else if (is_tag()){ 22 | title = __('tag') + ': ' + page.tag; 23 | } 24 | %> 25 | <% if (title){ %><%= title %> | <% } %><%= config.title %> 26 | 27 | <%- open_graph({twitter_id: theme.twitter, google_plus: theme.google_plus, fb_admins: theme.fb_admins, fb_app_id: theme.fb_app_id}) %> 28 | <% if (theme.rss){ %> 29 | 30 | <% } %> 31 | <% if (theme.favicon){ %> 32 | 33 | <% } %> 34 | 35 | <%- css('css/font-awesome.min') %> 36 | <%- css('css/bootstrap.min') %> 37 | <%- css('css/style') %> 38 | <%- css('css/site') %> 39 | <%- css('css/article') %> 40 | <%- css('css/code') %> 41 | 42 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /themes/san/source/img/Shape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /themes/san/source/img/b_trail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b_trail 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /themes/san/source/img/github2.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | github 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /source/_posts/practice/array-deep-updates-trigger-view.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数组深层更新如何触发视图更新? 3 | categories: 4 | - practice 5 | --- 6 | 7 | 在 San 组件中,对数据的变更需要通过`set` 或 `splice` 等方法,实现用最简单的方式,解决兼容性的问题,同时为了保证数据操作的过程可控,San 的数据变更在内部是 Immutable 的,因此遇到数组深层做数据交换时直接 set 数据会发现没有触发视图的更新 8 | 9 | 10 | 11 | #### 场景描述 12 | ```javascript 13 | class MyApp extends san.Component { 14 | static template = ` 15 |
16 |
点我交换数据
19 | 22 |
23 | `; 24 | initData() { 25 | return { 26 | list: [ 27 | { 28 | title: 'test1' 29 | }, 30 | { 31 | title: 'test2' 32 | } 33 | 34 | ] 35 | }; 36 | } 37 | handlerClick() { 38 | 39 | // 想交换两个值 40 | let firstNews = this.data.get('list'); 41 | let firstData = firstNews[0]; 42 | let secondData = firstNews[1]; 43 | firstNews[1] = firstData; 44 | firstNews[0] = secondData; 45 | 46 | // 在这里直接set数据发现并没有触发视图的更新 47 | this.data.set('list', firstNews); 48 | } 49 | } 50 | 51 | let myApp = new MyApp(); 52 | myApp.attach(document.body); 53 | 54 | ``` 55 | #### 原因分析 56 | San 的数据是 Immutable 的,因此 set firstNews 时变量的引用没变, diff 的时候还是相等的,不会触发更新。 57 | 58 | #### 解决方式如下 59 | 60 |

See the Pen 69 | 数组深层更新触发视图更新 70 | by solvan(@sw811) on 71 | CodePen.

72 | 73 | 74 | -------------------------------------------------------------------------------- /themes/san/source/img/search02.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /themes/san/source/img/5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /themes/san/source/img/github.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shape 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /themes/san/source/img/8.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /source/_posts/tutorial/ssr.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 服务端渲染 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 8 | 9 | San 的服务端渲染支持是基于 [组件反解](../reverse/) 的: 10 | 11 | - 服务端输出的 HTML 中带有对视图无影响,能帮助组件了解数据与视图结构的标记片段 12 | - 浏览器端,组件初始化时从标记片段理解组件结构,在后续用户操作时组件能正确响应,发挥作用 13 | 14 | `提示`:由于组件运行环境需要考虑浏览器各版本和NodeJS,示例代码为保证简单无需transform,全部采用ES5编写。 15 | 16 | 是否需要SSR 17 | ---- 18 | 19 | 服务端渲染,视图 HTML 直出有一些显而易见的好处: 20 | 21 | - SEO 友好,HTML 直接输出对搜索引擎的抓取和理解更有利 22 | - 用户能够第一时间看到内容。从开发者角度来说,首屏时间更快到来 23 | 24 | 但是,如果使用服务端渲染,我们将面对: 25 | 26 | - 更高的成本。虽然我们开发的是一份组件,但我们需要考虑其运行时是 NodeJS 和 浏览器 双端的,我们需要考虑在服务端渲染要提前编译,我们需要考虑组件的源码如何输出到浏览器端,我们需要考虑开发组件的浏览器兼容性,到底要写老旧的浏览器兼容性好的代码还是按ESNext写然后通过打包编译时 transform。这依然带来了维护成本的增加,即使不多 27 | - 用户可交互时间不一定更早到来。交互行为是由组件管理的,组件从当前视图反解出数据和结构需要遍历 DOM 树,反解的时间不一定比在前端直接渲染要快 28 | 29 | 所以,我们建议,使用服务端渲染时全面评估,只在必须使用的场景下使用。下面是一些场景建议: 30 | 31 | - 后台系统(CMS、MIS、DashBoard之类)大多使用 Single Page Application 的模式。显而易见,这类系统不需要使用 SSR 32 | - 功能型页面,不需要使用 SSR。比如个人中心、我的收藏之类 33 | - 仅在 App 的 WebView 中展现,不作为开放 Web 存在的页面,不需要使用 SSR 34 | - 偏重内容型页面,可以使用 SSR。但是组件是管理行为交互的,对内容部分无需进行组件渲染,只需要在有交互的部分进行组件反解渲染 35 | 36 | 37 | 输出HTML 38 | ---- 39 | 40 | ```javascript 41 | var MyComponent = san.defineComponent({ 42 | template: '{{name}}' 43 | }); 44 | 45 | var render = san.compileToRenderer(MyComponent); 46 | render({ 47 | email: 'errorrik@gmail.com', 48 | name: 'errorrik' 49 | }); 50 | // render html result: 51 | // .... 52 | ``` 53 | 54 | 55 | San 在主包下提供了 **compileToRenderer** 方法。该方法接收组件的类作为参数,编译返回一个 **{string}render({Object} data)** 方法。 **render** 方法接收数据,返回组件渲染后的 HTML 字符串。 56 | 57 | 58 | 编译NodeJS模块 59 | ---- 60 | 61 | 有时候,我们希望组件编译的 render 方法是一个单独的 NodeJS Module,以便于其他模块引用它。通过 San 主包下提供的 **compileToSource** 方法我们可以编译 NodeJS Module。 62 | 63 | ```javascript 64 | var san = require('san'); 65 | var fs = require('fs'); 66 | 67 | var MyComponent = san.defineComponent({ 68 | template: '{{name}}' 69 | }); 70 | 71 | var renderSource = san.compileToSource(MyComponent); 72 | fs.writeFileSync('your-module.js', 'exports = module.exports = ' + renderSource, 'UTF-8'); 73 | ``` 74 | 75 | **compileToSource** 方法接收组件的类作为参数,编译返回组件 render 的 source code,具体为 `function (data) {...}` 形式的字符串。我们只需要在前面增加 `exports = module.exports = `,并写入 **.js** 文件中,就能得到一个符合 CommonJS 标准的 NodeJS Module。 76 | -------------------------------------------------------------------------------- /themes/san/source/img/3.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 3 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /source/_posts/practice/dynamic-parent-child.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 动态子组件如何传递消息给父组件? 3 | categories: 4 | - practice 5 | --- 6 | 7 | 组件的创建中,可能需要在运行时,通过状态树渲染出一个动态组件树。通常的方法,我们通过 **dispatch/message** 但是由于父组件及子组件都是单独动态创建的,因此父子组件之间实际上是没有父子关系的,因此需要将子组件的parentComponent指向父组件,以实现动态父子组件之间的消息传递。 8 | 9 | #### example 10 | 11 | 此处给一个简单的例子,我们需要根据一个简单的状态树实现一个相应的组件样式,并实现父子组件的通信: 12 | 13 | ```javascript 14 | const Child = san.defineComponent({ 15 | template: ` 16 |
17 | {{name}} 18 |
19 | `, 20 | sendMsg() { 21 | this.dispatch('child-msg', this.data.get('msg')); 22 | } 23 | }); 24 | 25 | const Parent = san.defineComponent({ 26 | template: ` 27 |
28 | I am parent 29 | 32 | {{childMsg}} 33 |
`, 34 | 35 | addChild() { 36 | 37 | const childs = this.data.get('childs'); 38 | const parentEl = this.el; 39 | 40 | childs.forEach(child => { 41 | 42 | let childIns = new Child({ 43 | parent: this, 44 | data: child 45 | }); 46 | 47 | childIns.attach(parentEl); 48 | this.children.push(childIns); 49 | 50 | }); 51 | }, 52 | 53 | messages: { 54 | 'child-msg': function(arg) { 55 | this.data.set('childMsg', arg.value); 56 | } 57 | } 58 | }); 59 | 60 | const parent = new Parent({ 61 | data: { 62 | childs: [{ 63 | name: 'I am child1', 64 | msg: 'child1 send msg' 65 | }, { 66 | name: 'I am child2', 67 | msg: 'child2 send msg' 68 | }] 69 | } 70 | }); 71 | 72 | parent.attach(document.body); 73 | ``` 74 | 75 | #### 实例 76 | 77 |

See the Pen QMMZPV by zhanfang (@zhanfang) on CodePen.

78 | 79 | -------------------------------------------------------------------------------- /source/_posts/en/practice/child-to-grandparent.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How child components communicate with higher layer components 3 | categories: 4 | - practice 5 | --- 6 | 7 | #### usage 8 | 9 | The child component dispatches messages to the upper layer of the component tree via the **dispatch** method. 10 | 11 | 12 | ```javascript 13 | class Son extends san.Component { 14 | static template = ` 15 |
16 | 17 |
18 | `; 19 | 20 | onClick() { 21 | const value = this.data.get('value'); 22 | // Distribute messages to the upper level of the component tree 23 | this.dispatch('son-clicked', value); 24 | } 25 | }; 26 | ``` 27 | 28 | The message will be passed up the component tree until it encounters the first component that processes the message. Use **messages** to declare the message the component will process. **messages** is an object, the key is the name of a message, and value is the processing function of a message, which receives an object parameter containing `target` (the component that dispatches the message) and `value`(the value of the message). 29 | 30 | ```javascript 31 | class GrandParent extends san.Component { 32 | static template = '
'; 33 | 34 | // Declare messages that the component will process 35 | static messages = { 36 | 'son-clicked': function (arg) { 37 | // arg.target can get the component dispatched by the message 38 | // arg.value can get the value of the message 39 | this.data.set('value', arg.value); 40 | 41 | } 42 | } 43 | }; 44 | ``` 45 | 46 | #### examples 47 | 48 |

See the Pen higher-communication by Swan (@jiangjiu8357) on CodePen.

49 | 50 | -------------------------------------------------------------------------------- /source/_posts/tutorial/start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 开始 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | San,是一个 MVVM 的组件框架。它体积小巧(< 15K),兼容性好(IE6),性能卓越,是一个可靠、可依赖的实现响应式用户界面的解决方案。 8 | 9 | San 通过声明式的类 HTML 视图模板,在支持所有原生 HTML 的语法特性外,还支持了数据到视图的绑定指令、业务开发中最常使用的分支、循环指令等,在保持良好的易用性基础上,由框架完成基于字符串的模板解析,并构建出视图层的 [节点关系树](https://github.com/baidu/san/blob/master/doc/anode.md),通过高性能的视图引擎快速生成 UI 视图。San 中定义的数据会被封装,使得当数据发生有效变更时通知 San 组件,San 组件依赖模板编译阶段生成的[节点关系树](https://github.com/baidu/san/blob/master/doc/anode.md),确定需要变更的最小视图,进而完成视图的异步更新,保证了视图更新的高效性。 10 | 11 | 组件是 San 的基本单位,是独立的数据、逻辑、视图的封装单元。从页面角度看,组件是 HTML 元素的扩展;从功能模式角度看,组件是一个 ViewModel。San 组件提供了完整的生命周期,与 WebComponent 的生命周期相符合,组件间是可嵌套的树形关系,完整的支持了组件层级、组件间的通信,方便组件间的数据流转。San 的组件机制,可以有效支撑业务开发上的组件化需求。 12 | 13 | San 支持[组件反解](https://baidu.github.io/san/tutorial/reverse/),以此提供[服务端渲染](https://baidu.github.io/san/tutorial/ssr/)能力,可以解决纯前端渲染导致的响应用户交互时延长、SEO 问题。除此之外,San 还提供了一些周边开源产品,与 San 配合使用,可以帮助开发者快速搭建可维护的大型 SPA 应用。 14 | 15 | 现在,我们从一些简单的例子,开始了解 San。这些例子可以从[这里](https://github.com/baidu/san/tree/master/example/start)找到。 16 | 17 | Hello 18 | ------- 19 | 20 | ```javascript 21 | var MyApp = san.defineComponent({ 22 | template: '

Hello {{name}}!

', 23 | 24 | initData: function () { 25 | return { 26 | name: 'San' 27 | }; 28 | } 29 | }); 30 | 31 | 32 | var myApp = new MyApp(); 33 | myApp.attach(document.body); 34 | ``` 35 | 36 | 37 | 可以看到,通常情况使用 San 会经过这么几步: 38 | 39 | 1. 我们先定义了一个 San 的组件,在定义时指定了组件的 **内容模板** 与 **初始数据** 。 40 | 2. 初始化组件对象 41 | 3. 让组件在相应的地方渲染 42 | 43 | 44 | `额外提示`:在 JavaScript 中书写 HTML 片段对维护来说是不友好的,我们可以通过 WebPack、AMD plugin、异步请求等方式管理。这里为了例子的简单就写在一起了。 45 | 46 | 47 | 列表渲染 48 | -------- 49 | 50 | ```javascript 51 | var MyApp = san.defineComponent({ 52 | template: '', 53 | 54 | attached: function () { 55 | this.data.set('list', ['san', 'er', 'esui', 'etpl', 'esl']); 56 | } 57 | }); 58 | 59 | var myApp = new MyApp(); 60 | myApp.attach(document.body); 61 | ``` 62 | 63 | 上面的例子使用 for 指令对列表进行遍历并输出。这里有个很常用的实践方法:在生命周期 **attached** 中重新灌入数据,使视图刷新。在这里,我们可以发起获取数据的请求,在请求返回后更新数据。 64 | 65 | 66 | 双向绑定 67 | -------- 68 | 69 | ```javascript 70 | var MyApp = san.defineComponent({ 71 | template: '' 72 | + '
' 73 | + '' 74 | + 'Hello {{name}}!' 75 | + '
' 76 | }); 77 | 78 | var myApp = new MyApp(); 79 | myApp.attach(document.body); 80 | ``` 81 | 82 | 双向绑定通常出现在用户输入的场景,将用户输入结果自动更新到组件数据。在这个例子中,通过 **{= expression =}** 声明双向绑定,把输入框的 value 与数据项 name 绑定起来。 83 | 84 | 85 | -------------------------------------------------------------------------------- /themes/san/source/img/b_store.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b_store 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /source/_posts/en/practice/dynamic-parent-child.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Messaging from Dynamic Child Components 3 | categories: 4 | - practice 5 | --- 6 | 7 | Child components can be created dynamically using the runtime data. **dispatch/message** won't work cause there's no parent-child relationship between the current component and the newly created one. For a fix, all we need to do is building the relationship. 8 | 9 | #### example 10 | 11 | Here's a example in which we need to create a tree of components from the tree structure in the data, and pass messages between the parent and children: 12 | 13 | ```javascript 14 | const Child = san.defineComponent({ 15 | template: ` 16 |
17 | {{name}} 18 |
19 | `, 20 | sendMsg() { 21 | this.dispatch('child-msg', this.data.get('msg')); 22 | } 23 | }); 24 | 25 | const Parent = san.defineComponent({ 26 | template: ` 27 |
28 | I am parent 29 | 32 | {{childMsg}} 33 |
`, 34 | 35 | addChild() { 36 | 37 | const childs = this.data.get('childs'); 38 | const parentEl = this.el; 39 | 40 | childs.forEach(child => { 41 | 42 | let childIns = new Child({ 43 | parent: this, 44 | data: child 45 | }); 46 | 47 | childIns.attach(parentEl); 48 | this.children.push(childIns); 49 | 50 | }); 51 | }, 52 | 53 | messages: { 54 | 'child-msg': function(arg) { 55 | this.data.set('childMsg', arg.value); 56 | } 57 | } 58 | }); 59 | 60 | const parent = new Parent({ 61 | data: { 62 | childs: [{ 63 | name: 'I am child1', 64 | msg: 'child1 send msg' 65 | }, { 66 | name: 'I am child2', 67 | msg: 'child2 send msg' 68 | }] 69 | } 70 | }); 71 | 72 | parent.attach(document.body); 73 | ``` 74 | 75 | #### Demo 76 | 77 |

See the Pen QMMZPV by zhanfang (@zhanfang) on CodePen.

78 | 79 | -------------------------------------------------------------------------------- /themes/san/source/js/stickUp.min.js: -------------------------------------------------------------------------------- 1 | jQuery(function($){$(document).ready(function(){var contentButton = [];var contentTop = [];var content = [];var lastScrollTop = 0;var scrollDir = '';var itemClass = '';var itemHover = '';var menuSize = null;var stickyHeight = 0;var stickyMarginB = 0;var currentMarginT = 0;var topMargin = 0;$(window).scroll(function(event){var st = $(this).scrollTop();if (st > lastScrollTop){scrollDir = 'down';} else {scrollDir = 'up';}lastScrollTop = st;});$.fn.stickUp = function( options ) {$(this).addClass('stuckMenu');var objn = 0;if(options != null) {for(var o in options.parts) {if (options.parts.hasOwnProperty(o)){content[objn] = options.parts[objn];objn++;}}if(objn == 0) {console.log('error:needs arguments');}itemClass = options.itemClass;itemHover = options.itemHover;if(options.topMargin != null) {if(options.topMargin == 'auto') {topMargin = parseInt($('.stuckMenu').css('margin-top'));} else {if(isNaN(options.topMargin) && options.topMargin.search("px") > 0){topMargin = parseInt(options.topMargin.replace("px",""));} else if(!isNaN(parseInt(options.topMargin))) {topMargin = parseInt(options.topMargin);} else {console.log("incorrect argument, ignored.");topMargin = 0;} }} else {topMargin = 0;}menuSize = $('.'+itemClass).size();}stickyHeight = parseInt($(this).height());stickyMarginB = parseInt($(this).css('margin-bottom'));currentMarginT = parseInt($(this).next().closest('div').css('margin-top'));vartop = parseInt($(this).offset().top);};$(document).on('scroll', function() {varscroll = parseInt($(document).scrollTop());if(menuSize != null){for(var i=0;i < menuSize;i++){contentTop[i] = $('#'+content[i]+'').offset().top;function bottomView(i) {contentView = $('#'+content[i]+'').height()*.4;testView = contentTop[i] - contentView;if(varscroll > testView){$('.'+itemClass).removeClass(itemHover);$('.'+itemClass+':eq('+i+')').addClass(itemHover);} else if(varscroll < 50){$('.'+itemClass).removeClass(itemHover);$('.'+itemClass+':eq(0)').addClass(itemHover);}}if(scrollDir == 'down' && varscroll > contentTop[i]-50 && varscroll < contentTop[i]+50) {$('.'+itemClass).removeClass(itemHover);$('.'+itemClass+':eq('+i+')').addClass(itemHover);}if(scrollDir == 'up') {bottomView(i);}}}if(vartop < varscroll + topMargin){$('.stuckMenu').addClass('isStuck');$('.stuckMenu').next().closest('div').css({'margin-top': stickyHeight + stickyMarginB + currentMarginT + 'px'}, 10);$('.stuckMenu').css("position","fixed");$('.isStuck').css({top: '0px'}, 10, function(){});};if(varscroll + topMargin < vartop){$('.stuckMenu').removeClass('isStuck');$('.stuckMenu').next().closest('div').css({'margin-top': currentMarginT + 'px'}, 10);$('.stuckMenu').css("position","relative");};});});}); -------------------------------------------------------------------------------- /themes/san/source/js/script.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | //SmothScroll 3 | $('a[href*=#]').click(function() { 4 | if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') 5 | && location.hostname == this.hostname) { 6 | var $target = $(this.hash); 7 | $target = $target.length && $target || $('[name=' + this.hash.slice(1) +']'); 8 | if ($target.length) { 9 | var targetOffset = $target.offset().top; 10 | $('html,body').animate({scrollTop: targetOffset}, 600); 11 | return false; 12 | } 13 | } 14 | }); 15 | 16 | //scroll change nav color 17 | // window.onscroll = function () { 18 | // let scroll = document.body.scrollTop || document.documentElement.scrollTop; 19 | // let nava1 = document.getElementsByClassName('nav-a1')[0]; 20 | 21 | // if (scroll >= 60 && scroll < 120) { 22 | // if (!(/navscroll/.test(nava1.className))) { 23 | // nava1.className += ' navscroll'; 24 | // debugger; 25 | // } 26 | // } 27 | 28 | // else { 29 | // nava1.className = 'nav-a1'; 30 | // } 31 | // }; 32 | 33 | 34 | //bodymovin control 35 | var resourceCards = document.querySelectorAll('.resource-block'); 36 | var facilityCards = document.querySelectorAll('.facility-block'); 37 | var len = resourceCards.length; 38 | setBodymovin = function(cards, len){ 39 | while (len--) { 40 | var bodymovinLayer = cards[len].getElementsByClassName('bodymovin')[0]; 41 | 42 | var animData = { 43 | wrapper: bodymovinLayer, 44 | loop: false, 45 | prerender: true, 46 | autoplay: false, 47 | path: bodymovinLayer.getAttribute('data-movpath') 48 | }; 49 | 50 | anim = bodymovin.loadAnimation(animData); 51 | 52 | (function(anim){ 53 | var card = cards[len]; 54 | $(card).on('mouseenter', function(){ 55 | anim.play(); 56 | }); 57 | 58 | $(card).on('mouseleave', function(e){ 59 | anim.stop(); 60 | }); 61 | 62 | })(anim); 63 | } 64 | } 65 | setBodymovin(resourceCards, len); 66 | setBodymovin(facilityCards, len); 67 | 68 | }); 69 | 70 | -------------------------------------------------------------------------------- /source/_posts/en/practice/array-deep-updates-trigger-view.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: How does an array deep update trigger a view update? 3 | categories: 4 | - practice 5 | --- 6 | 7 | In the San component, changes to the data need to be done by methods such as `set` or `splice`. San's implementation uses the easiest way to solve compatibility problems, and in order to ensure that the data manipulation process is controllable, San's data changes are internally Immutable. Therefore, when encountering data manipulation in a deep array, directly calling set to modify the data will find that the view does not trigger an update. 8 | 9 | #### Scene description 10 | ```javascript 11 | class MyApp extends san.Component { 12 | static template = ` 13 |
14 |
Point me to exchange data
17 | 20 |
21 | `; 22 | initData() { 23 | return { 24 | list: [ 25 | { 26 | title: 'test1' 27 | }, 28 | { 29 | title: 'test2' 30 | } 31 | 32 | ] 33 | }; 34 | } 35 | handlerClick() { 36 | 37 | // want to exchange two values 38 | let firstNews = this.data.get('list'); 39 | let firstData = firstNews[0]; 40 | let secondData = firstNews[1]; 41 | firstNews[1] = firstData; 42 | firstNews[0] = secondData; 43 | 44 | // The data set directly here does not trigger the update of the view. 45 | this.data.set('list', firstNews); 46 | } 47 | } 48 | 49 | let myApp = new MyApp(); 50 | myApp.attach(document.body); 51 | 52 | ``` 53 | #### Cause Analysis 54 | The data of San is Immutable, so the reference to the variable does not change when set firstNews, and the diff is still equal and does not trigger an update. 55 | 56 | #### Resolutions 57 | 58 |

See the Pen 67 | Array deep update triggers view update 68 | by solvan(@sw811) on 69 | CodePen.

70 | 71 | 72 | -------------------------------------------------------------------------------- /source/_posts/en/tutorial/if.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Conditional Rendering 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 8 | s-if 9 | ------ 10 | 11 | **s-if** directive can be used for conditional rendering. The element will be rendered if and only if the condition evaluates to true. 12 | 13 | `Note`: The element will be removed rather than hidden when the condition is not satisfied. 14 | 15 | ```html 16 | Hello San! 17 | ``` 18 | 19 | The **s-if** directive can contain any type of [expression](../template/#Expression). 20 | 21 | ```html 22 | Hello San! 23 | ``` 24 | 25 | `Note`:All JavaScript falsy values are considered false: 0, empty string, null, undefined, NaN, etc. 26 | 27 | s-elif 28 | ------ 29 | 30 | `> 3.2.3` 31 | 32 | **s-elif** directive can be used to add a condition branch for **s-if**. The **s-elif** directive can contain any type of [expression](../template/#Expression). 33 | 34 | ```html 35 | Active 36 | Pending 37 | ``` 38 | 39 | `Note`:**s-elif** element must immediately follow a **s-if** or **s-elif** element. An **elif not match if** exception will be thrown if used otherwise. 40 | 41 | 42 | s-else-if 43 | ------ 44 | 45 | `> 3.5.6` 46 | 47 | **s-else-if** directive is an alias for **s-elif** directive, they're effectively the same. 48 | 49 | ```html 50 | Active 51 | Pending 52 | ``` 53 | 54 | 55 | s-else 56 | ------ 57 | 58 | **s-else** directive can be used to specify a condition-not-satisfied branch for **s-if**. **s-else** have no value. 59 | 60 | ```html 61 | Hello! 62 | Offline 63 | ``` 64 | 65 | `Note`:**s-else** element must immediately follow a **s-if** or **s-elif** element. An **else not match if** exception will be thrown if used otherwise. 66 | 67 | 68 | Virtual Element 69 | ------ 70 | 71 | In san templates, only contents of **template** are rendered, NOT including the **template** element itself. By applying an **if** directive for **template** element, multiple elements (without a redundant parent element) can be rendered regarding to the **if** condition. 72 | 73 | ```html 74 |
75 | 79 | 83 | 87 | 91 |
92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /themes/san/source/img/4.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 4 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /source/_posts/en/tutorial/style.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Style 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | Style processing is a common scenario when writing view templates. The attributes involved are class and style, and they are handled differently from other element attributes. This article specifically describes the common scenarios in style processing. 8 | 9 | Before starting, let's emphasize that San does not provide a special binding syntax for class and style processing, and they handle the same way as other attributes. 10 | 11 | class 12 | ------ 13 | 14 | We might design some class to represent the state, whether these classes should be added to the element, depending on the value of some data. A simple scenario is the unfolding and unfolding state of a drop-down list. 15 | 16 | ```html 17 | 18 |
19 | 20 | 21 |
22 | ``` 23 | 24 | ```javascript 25 | // Component 26 | san.defineComponent({ 27 | toggle: function () { 28 | var isHidden = this.data.get('isHidden'); 29 | this.data.set('isHidden', !isHidden); 30 | } 31 | }); 32 | ``` 33 | 34 | In the above example, `list-hidden` class appears when `isHidden` is `true`, otherwise it is omitted 35 | 36 | When designing, San wants the view template developer to write class and style just like a normal attribute, so no special binding syntax is provided. This can be achieved by the ternary operator. 37 | 38 | The following example is a scenario where different classes are switched depending on the state. 39 | 40 | 41 | ```html 42 | 43 | ``` 44 | 45 | style 46 | ----- 47 | 48 | The handling of `style` is usually not as complicated as `class`. We rarely write style information in the data, but sometimes we expect users to be able to customize some interface styles, at which point the style may be derived from data. 49 | 50 | ```html 51 | 59 | ``` 60 | 61 | The caveat is that the data may not exist, and the style you set is not a legal style. If you can't guarantee that the data must have a value, you need to include the style name in the interpolation. 62 | 63 | ```html 64 | 72 | ``` 73 | -------------------------------------------------------------------------------- /themes/san/source/img/9.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /themes/san/source/img/b_api.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b_api 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /source/_posts/practice/can-we-use-dom.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 我们可以操作 DOM 吗? 3 | categories: 4 | - practice 5 | --- 6 | 7 | 8 | 我们在使用 San 的时候,特别是刚刚使用不久的新人,且 MVVM 框架的经验不是那么丰富,我们还是更习惯于使用 jQuery 作为类库来操作页面的交互,于是很自然的写出了这样的代码。 9 | 10 | ``` 11 | var MyApp = san.defineComponent({ 12 | template: '', 13 | attached: function () { 14 | this.bindSomeEvents(); 15 | }, 16 | bindSomeEvents: function () { 17 | $('.btn').click(()=>{ 18 | $('.ipt').val('点了'); 19 | }); 20 | } 21 | }); 22 | var myApp = new MyApp(); 23 | myApp.attach(document.querySelector('#app')); 24 | ``` 25 | 26 | 然后用浏览器运行了这段程序,结果完全符合预期,完美~ 27 | 28 | 然而当我们进一步熟悉了 San 的使用方式后,对于上面的功能我们会写出这样的代码。 29 | 30 | ``` 31 | var MyApp = san.defineComponent({ 32 | template: '
', 33 | initData: function () { 34 | return { 35 | value: '没点' 36 | }; 37 | }, 38 | clickHandler: function () { 39 | this.data.set('value', '点了') 40 | } 41 | }); 42 | var myApp = new MyApp(); 43 | myApp.attach(document.querySelector('#app')); 44 | ``` 45 | 46 | 仔细推敲了下这两段代码,不禁产生了一个疑问。 47 | 48 | 直观的来看,San 的代码中我们直接调用 this.data.set 来修改某个属性的值,它自动将修改后的内容渲染到了 DOM 上,似乎看起来非常的神奇,但是它的根本上还是对 DOM 进行的操作,只不过这个操作是San框架帮你完成的,既然是这样,那我们为什么不能直接像第一段代码一样,直接修改,而要把这些操作交给 San 来完成呢?如果从性能上考虑交给 San 来做,它要完成从 Model 到视图上的关系绑定,还需要有一部分性能的损失,这样看起来代价还挺大的,那我们为什么还要这么做呢? 49 | 50 | 带着这个问题,我们可以从这几方面进行考虑。 51 | 52 | ### 使用 San 的初衷? 53 | 54 | San 是一个 MVVM(Model-View-ViewModel) 的组件框架,借助 MVVM 框架,我们只需完成包含 **声明绑定** 的视图模板,编写 ViewModel 中业务数据变更逻辑,View 层则完全实现了自动化。这将极大的降低前端应用的操作复杂度、极大提升应用的开发效率。MVVM 最标志性的特性就是 **数据绑定** ,MVVM 的核心理念就是通过 **声明式的数据绑定** 来实现 View 层和其他层的分离,完全解耦 View 层这种理念,也使得 Web 前端的单元测试用例编写变得更容易。 55 | 56 | 简单来说就是:操作数据,就是操作视图,也就是操作 DOM。 57 | 58 | ### 此 DOM 非彼 DOM 59 | 60 | 在我们写的代码中的 template 属性,在 San 中被称作 **内容模板**,它是一个符合 HTML 语法规则的字符串,它会被 San 解析,返回一个 [ANode](https://github.com/baidu/san/blob/master/doc/anode.md) 对象。 61 | 62 | 也就是说我们在 template 中写的东西实际上并不是要放到 DOM 上的,它是给 San 使用的,真正生成的 DOM 实际上是 San 根据你的 template 的解析结果也就是 [ANode](https://github.com/baidu/san/blob/master/doc/anode.md) 生成的,你的代码与DOM之间其实还隔了一层 San。 63 | 64 | 我们如果直接使用原生的 api 或者 jQuery 来直接操作 San 生成的DOM,这是不合理的,因为那些DOM根本不是我们写的,而我们却要去试图修改它,显然我们不应该这样做。 65 | 66 | 不直接操做 DOM 这其实也是符合计算机领域中分层架构设计的基本原则的,每一层完成独立的功能,然后上层通过调用底层的 api 来使用底层暴露出来的功能,但禁止跨层的调用。 67 | 68 | ### 有时候我们过度的考虑了性能这个问题 69 | 70 | San 框架极大的提升了应用的开发效率,它帮我们屏蔽繁琐的 DOM 操作,帮我们处理了 Model 与 View的关系,这看起来真的很美好,但一切美好的事情总是要付出代价的,San要做这些,就会带来性能上的开销,所以它用起来比直接操做 DOM 性能要差,这是毋庸置疑的,世界上也不可能存在这种框架性能比直接操作 DOM 还要好,如果你要改变一个页面的显示状态,DOM 是它的唯一 API,任何框架都不可能绕过。 71 | 72 | 但这种性能上的消耗真的给我的应用带来的不可维护的问题了吗,反而是大部分原因是因为我们在开发中代码结构的不合理,代码不够规范,功能划分不够清晰,等一系列主观上的问题导致的项目无法维护下去。 73 | 74 | ### 总之 75 | 76 | 在我们的项目中选择 San 做为框架,它不仅可以让你从繁琐的 DOM 操作中解脱出来,通过 MVVM 的模式极大的降低前端应用的操作复杂度、极大提升应用的开发效率,它的组件系统作为一个独立的数据、逻辑、视图的封装单元更是能够帮你很好的在开发中梳理好应用的代码结构,保证系统能够更加易于维护。 77 | 78 | 79 | -------------------------------------------------------------------------------- /themes/san/source/img/b_mater.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b8 copy 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /source/_posts/tutorial/form.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 表单 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 8 | 表单是常见的用户输入承载元素,本篇介绍一些常用表单元素的用法。在 MVVM 中,我们一般在用户输入的表单元素或组件上应用 **双向绑定**。 9 | 10 | 11 | 输入框 12 | ----- 13 | 14 | 输入框的绑定方法比较简单,直接对输入框的 value 属性应用双向绑定就行了。 15 | 16 | 17 | ```html 18 | 19 | ``` 20 | 21 | 22 | checkbox 23 | ------- 24 | 25 | checkbox 常见的使用场景是分组,在组件模板中,我们把需要分组的 checkbox 将 checked 属性双向绑定到同名的组件数据中。 26 | 27 | `提示`:除非你需要进行传统的表单提交,否则无需指定 checkbox 的 name 属性。San 仅以 checked 作为分组的依据。 28 | 29 | ```html 30 | 31 |
32 | 33 | 34 | 35 |
36 | ``` 37 | 38 | 我们期望 checkbox 绑定到的数据项是一个 **Array<string>** 。当 checkbox 被选中时,其 value 会被添加到绑定的数据项中;当 checkbox 被取消选中时,其 value 会从绑定数据项中移除。 39 | 40 | ```js 41 | // Component 42 | san.defineComponent({ 43 | // ... 44 | 45 | initData: function () { 46 | return { 47 | online: [] 48 | }; 49 | }, 50 | 51 | attached: function () { 52 | this.data.set('online', ['errorrik', 'otakustay']); 53 | } 54 | }); 55 | 56 | ``` 57 | 58 | 59 | 60 | radio 61 | ----- 62 | 63 | 与 checkbox 类似,我们在组件模板中,把需要分组的 radio 将 checked 属性绑定到同名的组件数据中。 64 | 65 | `提示`:你需要手工指定分组 radio 的 name 属性,使浏览器能处理 radio 选择的互斥。可以把它设置成与绑定数据的名称相同。 66 | 67 | ```html 68 | 69 |
70 | 71 | 72 | 73 |
74 | ``` 75 | 76 | 我们期望 radio 绑定到的数据项是一个 **string** 。当 radio 被选中时,绑定的数据项值被设置成选中的 radio 的 value 属性值。 77 | 78 | ```js 79 | // Component 80 | san.defineComponent({ 81 | // ... 82 | 83 | initData: function () { 84 | return { 85 | online: 'errorrik' 86 | }; 87 | } 88 | }); 89 | ``` 90 | 91 | 92 | select 93 | ------ 94 | 95 | select 的使用方式和输入框类似,直接对 value 属性应用双向绑定。 96 | 97 | ```html 98 | 99 | 104 | ``` 105 | 106 | `提示`:在浏览器中,select 的 value 属性并不控制其选中项,select 的选中项是由 option 的 selected 属性控制的。考虑到开发的方便,开发者不需要编写 option 的 selected 属性,San 会在下一个视图更新时间片中刷新 select 的选中状态。 107 | 108 | ```js 109 | // Component 110 | san.defineComponent({ 111 | // ... 112 | 113 | initData: function () { 114 | return { 115 | online: 'errorrik' 116 | }; 117 | } 118 | }); 119 | ``` 120 | -------------------------------------------------------------------------------- /themes/san/source/js/update.json: -------------------------------------------------------------------------------- 1 | {"v":"4.12.2","fr":60,"ip":0,"op":48,"w":128,"h":128,"nm":"合成 6","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“b_update”轮廓 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":0,"s":[64,64,0],"e":[64,56,0],"to":[0,-1.33333337306976,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":20,"s":[64,56,0],"e":[64,64,0],"to":[0,0,0],"ti":[0,-1.33333337306976,0]},{"t":40}],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0],[0,0],[0,2.761],[-0.938,0.938],[0,0],[-1.953,-1.953],[0,0],[1.953,-1.953],[1.326,0]],"o":[[0,0],[0,0],[0,0],[0,0],[-2.761,0],[0,-1.326],[0,0],[1.953,-1.953],[0,0],[1.953,1.953],[-0.937,0.937],[0,0]],"v":[[83.184,55.184],[83.184,81.184],[46.184,81.184],[46.184,55.184],[31,55.184],[26,50.184],[27.465,46.648],[60.648,13.465],[67.72,13.465],[100.903,46.648],[100.903,53.72],[97.368,55.184]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"“b_update”轮廓 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":4,"s":[64,64,0],"e":[64,56,0],"to":[0,-1.33333337306976,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":24,"s":[64,56,0],"e":[64,64,0],"to":[0,0,0],"ti":[0,-1.33333337306976,0]},{"t":44}],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[46,98],[83,98],[83,86],[46,86]],"c":true},"ix":2},"nm":"路径 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":70,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"“b_update”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":8,"s":[64,64,0],"e":[64,56,0],"to":[0,-1.33333337306976,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":28,"s":[64,56,0],"e":[64,64,0],"to":[0,0,0],"ti":[0,-1.33333337306976,0]},{"t":48}],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,0],[2.209,0],[0,0],[0,2.209]],"o":[[0,0],[0,0],[0,2.209],[0,0],[-2.209,0],[0,0]],"v":[[46,103],[83,103],[83,111],[79,115],[50,115],[46,111]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":50,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0}]} -------------------------------------------------------------------------------- /source/_posts/tutorial/reverse.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 组件反解 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 8 | **3.4.0 对组件反解机制做了全面的升级。新的组件反解基于数据和模板匹配的机制,代替原来的标记机制。** 9 | 10 | 旧的基于标记的组件反解请见[老文档](../reverse-flag/) 11 | 12 | 13 | `版本`:>= 3.4.0 14 | 15 | 16 | `提示`:通过 San 进行[服务端渲染](../ssr/),一定能通过相同版本的 San 在浏览器端进行反解。 17 | 18 | 19 | 概述 20 | ---- 21 | 22 | 组件初始化时传入 **el**,其将作为组件根元素,并以此反解析出视图结构。 23 | 24 | 该特性的意图是:有时我们为了首屏时间,期望初始的视图是直接的 HTML,不由组件渲染。但是我们希望组件为我们管理数据、逻辑与视图,后续的用户交互行为与视图变换通过组件管理。 25 | 26 | ```javascript 27 | var myComponent = new MyComponent({ 28 | el: document.getElementById('wrap').firstChild 29 | }); 30 | ``` 31 | 32 | 以 **el** 初始化组件时,San 会尊重 **el** 的现时 HTML 形态,不会执行任何额外影响视觉的渲染动作。 33 | 34 | - 不会使用预设的 template 渲染视图 35 | - 不会创建根元素 36 | - 直接到达 compiled、created、attached 生命周期 37 | 38 | 39 | 40 | 41 | 数据 42 | ---- 43 | 44 | 组件的视图是数据的呈现。我们需要通过在组件起始时标记 **data**,以指定正确的初始数据。初始数据标记是一个 **s-data:** 开头的 HTML Comment,在其中声明数据。 45 | 46 | 47 | `警告`:San 的组件反解过程基于数据和组件模板进行视图结构反推与匹配。反解的组件必须拥有正确的标记数据,否则反解过程会发生错误。比如对于模板中的 s-if 条件进行视图反推,如果没有正确的标记数据,反推就会因为元素对应不上,得到不期望的结果。 48 | 49 | 50 | ```html 51 | 54 | errorrik 55 | 56 | ``` 57 | 58 | ```javascript 59 | var MyComponent = san.defineComponent({ 60 | template: '' 61 | + '\n' 62 | + ' {{name}}\n' 63 | + '' 64 | }); 65 | 66 | var myComponent = new MyComponent({ 67 | el: document.getElementById('wrap') 68 | }); 69 | ``` 70 | 71 | 72 | 73 | 如果一个组件拥有 owner,不用标记初始数据。其初始数据由 owner 根据绑定关系灌入。 74 | 75 | 76 | ```html 77 | 78 |
79 | errorrik 80 |
81 | ``` 82 | 83 | ```javascript 84 | var Label = san.defineComponent({ 85 | template: '{{text}}' 86 | }); 87 | 88 | var MyComponent = san.defineComponent({ 89 | components: { 90 | 'ui-label': Label 91 | }, 92 | 93 | template: '' 94 | + '
\n' 95 | + ' \n' 96 | + '
' 97 | }); 98 | 99 | var myComponent = new MyComponent({ 100 | el: document.getElementById('main') 101 | }); 102 | ``` 103 | 104 | 105 | 复合插值文本 106 | ---- 107 | 108 | San 支持在插值文本中直接输出 HTML,此时插值文本对应的不是一个 TextNode,而可能是多个不同类型的元素。对于这种复合插值文本,需要在内容的前后各添加一个注释做标记。 109 | 110 | - 文本前的注释内容为 **s-text**,代表插值文本片段开始。 111 | - 文本后的注释内容为 **/s-text**,代表插值文本片段结束。 112 | 113 | ```html 114 | 116 | Hello new San! 117 | 118 | ``` 119 | 120 | ```javascript 121 | var MyComponent = san.defineComponent({ 122 | template: '' 123 | + '\n' 124 | + ' Hello {{name|raw}}!\n' 125 | + '' 126 | }); 127 | 128 | var myComponent = new MyComponent({ 129 | el: document.getElementById('wrap') 130 | }); 131 | ``` 132 | -------------------------------------------------------------------------------- /themes/san/layout/_partial/header.ejs: -------------------------------------------------------------------------------- 1 |
2 | 3 | LOGO 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 30 |
31 |
32 | 33 | -------------------------------------------------------------------------------- /source/_posts/tutorial/background.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 背景 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 引言 8 | ----- 9 | 10 | 2008年,V8 引擎随 Chrome 浏览器横空出世,JavaScript 这门通用的 Web 脚本语言的执行效率得到质的提升。 V8 引擎的出现,注定是 JavaScript 发展史上一个光辉的里程碑。它的出现,让当时研究高性能服务器开发、长时间一筹莫展的 [Ryan Dahl](http://tinyclouds.org/) 有了新的、合适的选择,不久,在2009年的柏林的 JSConf 大会上,基于 JavaScript 的服务端项目 Node.js 正式对外发布。Node.js 的发布,不仅为开发者带来了一个高性能的服务器,还很大程度上推动了前端的工程化,带来了前端的大繁荣。与此同时,因为 JavaScript 执行效率的巨大提升,越来越多的业务逻辑开始在浏览器端实现,前端逻辑越来越重,前端架构随之提上日程。于是,我们谈论的主角,MVVM 模式,走进了 Web 前端的架构设计中。 11 | 12 | 概念 13 | ----- 14 | 15 | MVVM 模式,顾名思义即 Model-View-ViewModel 模式。它萌芽于2005年微软推出的基于 Windows 的用户界面框架 WPF ,前端最早的 MVVM 框架 [knockout](https://github.com/knockout/knockout) 在2010年发布。 16 | 17 | 一句话总结 Web 前端 MVVM:操作数据,就是操作视图,就是操作 DOM(所以无须操作 DOM )。 18 | 19 | 无须操作 DOM !借助 MVVM 框架,开发者只需完成包含 **声明绑定** 的视图模板,编写 ViewModel 中业务数据变更逻辑,View 层则完全实现了自动化。这将极大的降低前端应用的操作复杂度、极大提升应用的开发效率。MVVM 最标志性的特性就是 **数据绑定** ,MVVM 的核心理念就是通过 **声明式的数据绑定** 来实现 View 层和其他层的分离。完全解耦 View 层这种理念,也使得 Web 前端的单元测试用例编写变得更容易。 20 | 21 | MVVM,说到底还是一种分层架构。它的分层如下: 22 | 23 | - Model: 域模型,用于持久化 24 | - View: 作为视图模板存在 25 | - ViewModel: 作为视图的模型,为视图服务 26 | 27 | ### Model 层 28 | 29 | Model 层,对应数据层的域模型,它主要做`域模型的同步`。通过 Ajax/fetch 等 API 完成客户端和服务端业务 Model 的同步。在层间关系里,它主要用于抽象出 ViewModel 中视图的 Model。 30 | 31 | ### View 层 32 | 33 | View 层,作为视图模板存在,在 MVVM 里,整个 View 是一个动态模板。除了定义结构、布局外,它展示的是 ViewModel 层的数据和状态。View 层不负责处理状态,View 层做的是 **数据绑定的声明**、 **指令的声明**、 **事件绑定的声明**。 34 | 35 | ### ViewModel 层 36 | 37 | ViewModel 层把 View 需要的层数据暴露,并对 View 层的 **数据绑定声明**、 **指令声明**、 **事件绑定声明** 负责,也就是处理 View 层的具体业务逻辑。ViewModel 底层会做好绑定属性的监听。当 ViewModel 中数据变化,View 层会得到更新;而当 View 中声明了数据的双向绑定(通常是表单元素),框架也会监听 View 层(表单)值的变化。一旦值变化,View 层绑定的 ViewModel 中的数据也会得到自动更新。 38 | 39 | ### 前端 MVVM 图示 40 | 41 | 前端MVVM 42 | 43 | 如图所示,在前端 MVVM 框架中,往往没有清晰、独立的 Model 层。在实际业务开发中,我们通常按 **Web Component** 规范来组件化的开发应用,Model 层的域模型往往分散在在一个或几个 Component 的 ViewModel 层,而 ViewModel 层也会引入一些 View 层相关的中间状态,目的就是为了更好的为 View 层服务。 44 | 45 | 开发者在 View 层的视图模板中声明 **数据绑定**、 **事件绑定** 后,在 ViewModel 中进行业务逻辑的 **数据** 处理。事件触发后,ViewModel 中 **数据** 变更, View 层自动更新。因为 MVVM 框架的引入,开发者只需关注业务逻辑、完成数据抽象、聚焦数据,MVVM 的视图引擎会帮你搞定 View。因为数据驱动,一切变得更加简单。 46 | 47 | MVVM框架的工作 48 | ----- 49 | 50 | 不可置否,MVVM 框架极大的提升了应用的开发效率。It's amazing!But,MVVM 框架到底做了什么? 51 | 52 | - 视图引擎 53 | 54 | 视图引擎:我是视图引擎,我为 View 层作为视图模板提供强力支持,开发者,你们不需要操作 DOM ,丢给我来做! 55 | 56 | - 数据存取器 57 | 58 | 数据存取器:我是数据存取器,我可以通过 `Object.defineProperty()` API 轻松定义,或通过自行封装存取函数的方式曲线完成。我的内部往往封装了 **发布/订阅模式**,以此来完成对数据的监听、数据变更时通知更新。我是 **数据绑定** 实现的基础。 59 | 60 | - 组件机制 61 | 62 | 组件机制:我是组件机制。有追求的开发者往往希望按照面向未来的组件标准 - **Web Components** 的方式开发,我是为了满足你的追求而生。MVVM 框架提供组件的定义、继承、生命周期、组件间通信机制,为开发者面向未来开发点亮明灯。 63 | 64 | - more... 65 | 66 | 结语 67 | ----- 68 | 69 | >有了前端 MVVM 框架,应用开发如此简单! 70 | 71 | 前端 MVVM 已是趋势,是大型 Web 应用开发效率提升的利器。由百度 EFE 出品的 MVVM 框架 - [san](https://baidu.github.io/san/),在保持功能强大、特性支持完整的前提下,还兼顾到IE8的市场份额,对老版本浏览器提供了良好的兼容性,更难能可贵的是 GZip 后体积仅 **11k**, 现已为百度内多个产品提供了强劲驱动,可谓百度 EFE 又一精工之作!开源的 [san](https://baidu.github.io/san/) 欢迎广大开发者体验、使用,更欢迎广大开发者加入到 [san 生态](https://github.com/baidu?utf8=%E2%9C%93&q=san&type=&language=) 的建设中来,让 [san](https://baidu.github.io/san/) 变得更好! -------------------------------------------------------------------------------- /source/_posts/tutorial/for.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 循环 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 通过循环渲染列表是常见的场景。通过在元素上作用 **s-for** 指令,我们可以渲染一个列表。 8 | 9 | 10 | 语法 11 | ---- 12 | 13 | **s-for** 指令的语法形式如下: 14 | 15 | ``` 16 | item-identifier[, index-identifier] in expression[ trackBy accessor-expression] 17 | ``` 18 | 19 | 列表渲染 20 | ---- 21 | 22 | 下面的代码描述了在元素上作用 **s-for** 指令,渲染一个列表。在列表渲染的元素内部,可以正常访问到 owner 组件上的其他数据(下面例子中的dept)。 23 | 24 | ```html 25 | 26 |
27 |
name - email
28 |
{{p.name}}({{dept}}) - {{p.email}}
29 |
30 | ``` 31 | 32 | ```js 33 | // Component 34 | san.defineComponent({ 35 | // template 36 | 37 | initData: function () { 38 | return { 39 | dept: 'ssg', 40 | persons: [ 41 | {name: 'errorrik', email: 'errorrik@gmail.com'}, 42 | {name: 'otakustay', email: 'otakustay@gmail.com'} 43 | ] 44 | }; 45 | } 46 | }); 47 | ``` 48 | 49 | 索引 50 | ---- 51 | 52 | **s-for** 指令中可以指定索引变量名(下面例子中的index),从而在列表渲染过程获得列表元素的索引。 53 | 54 | ```html 55 | 56 |
57 |
name - email
58 |
{{index + 1}}. {{p.name}}({{dept}}) - {{p.email}}
59 |
60 | ``` 61 | 62 | ```js 63 | // Component 64 | san.defineComponent({ 65 | // template 66 | 67 | initData: function () { 68 | return { 69 | dept: 'ssg', 70 | persons: [ 71 | {name: 'errorrik', email: 'errorrik@gmail.com'}, 72 | {name: 'otakustay', email: 'otakustay@gmail.com'} 73 | ] 74 | }; 75 | } 76 | }); 77 | ``` 78 | 79 | 列表数据操作 80 | ------- 81 | 82 | 列表数据的增加、删除等操作请使用组件 data 提供的数组方法。详细请参考[数组方法](../data-method/#数组方法)文档。 83 | 84 | 85 | 虚拟元素 86 | ------ 87 | 88 | 和 if 指令一样,对 template 元素应用 for 指令,能够让多个元素同时根据遍历渲染,可以省掉一层不必要的父元素。 89 | 90 | 91 | ```html 92 | 93 |
94 | 98 |
99 | ``` 100 | 101 | trackBy 102 | ------ 103 | 104 | `>= 3.6.1` 105 | 106 | 107 | 在 **s-for** 指令声明中指定 **trackBy**,San 在数组更新时,将自动跟踪项的变更,进行相应的 insert/remove 等操作。 **trackBy** 只能声明 item-identifier 的属性访问。 108 | 109 | 110 | ```html 111 | 112 |
113 |
name - email
114 |
{{p.name}}({{dept}}) - {{p.email}}
115 |
116 | ``` 117 | 118 | ```js 119 | // Component 120 | san.defineComponent({ 121 | // template 122 | 123 | initData: function () { 124 | return { 125 | dept: 'ssg', 126 | persons: [ 127 | {name: 'errorrik', email: 'errorrik@gmail.com'}, 128 | {name: 'otakustay', email: 'otakustay@gmail.com'} 129 | ] 130 | }; 131 | } 132 | }); 133 | ``` 134 | 135 | 136 | trackBy 通常用在对后端返回的 JSON 数据渲染时。因为前后两次 JSON parse 无法对列表元素进行 === 比对,通过 trackBy, 框架将在内部跟踪变更。结合 transition 时,变更过程的动画效果将更符合常理。 137 | 138 | 在下面的场景下,使用 trackBy 的性能上反而会变差: 139 | 140 | - 所有数据项都发生变化 141 | - 数据项变化前后的顺序不同 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /source/_posts/tutorial/data-checking.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 数据校验 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 我们可以给组件的 data 指定校验规则。如果传入的数据不符合规则,那么 san 会抛出异常。当组件给其他人使用时,这很有用。 8 | 9 | 指定校验规则,需要使用 `DataTypes` 进行声明: 10 | 11 | ```js 12 | import san, {DataTypes} from 'san'; 13 | 14 | let MyComponent = san.defineComponent({ 15 | 16 | dataTypes: { 17 | name: DataTypes.string 18 | } 19 | 20 | }); 21 | ``` 22 | 23 | `DataTypes` 提供了一系列校验器,可以用来保证组件得到的数据是合法的。在上边的示例中,我们使用了 `DataTypes.string`。当一个 `name` 得到了一个不合法的数据值时,san 会抛出异常。 24 | 25 | **考虑到性能原因,`dataTypes` 只会在 `development` 模式下进行数据校验。** 26 | 27 | 请参考[这里](https://github.com/baidu/san/tree/master/dist)来确认在不同的 san 发布版本中数据校验功能的支持情况。 28 | 29 | ## DataTypes 30 | 31 | 下边是 `DataTypes` 提供的各种校验的一个示例代码: 32 | 33 | ```js 34 | import san, {DataTypes} from 'san'; 35 | 36 | san.defineComponent({ 37 | 38 | // 你可以声明数据为 JS 原生类型。 39 | // 默认的以下这些数据是可选的。 40 | optionalArray: DataTypes.array, 41 | optionalBool: DataTypes.bool, 42 | optionalFunc: DataTypes.func, 43 | optionalNumber: DataTypes.number, 44 | optionalObject: DataTypes.object, 45 | optionalString: DataTypes.string, 46 | optionalSymbol: DataTypes.symbol, 47 | 48 | // 你也可以声明数据为指定类的实例。 49 | // 这里会使用 instanceof 进行判断。 50 | optionalMessage: DataTypes.instanceOf(Message), 51 | 52 | // 如果你可以确定你的数据是有限的几个值之一,那么你可以将它声明为枚举类型。 53 | optionalEnum: DataTypes.oneOf(['News', 'Photos']), 54 | 55 | // 可以是指定的几个类型之一 56 | optionalUnion: DataTypes.oneOfType([ 57 | DataTypes.string, 58 | DataTypes.number, 59 | DataTypes.instanceOf(Message) 60 | ]), 61 | 62 | // 数组中每个元素都必须是指定的类型 63 | optionalArrayOf: DataTypes.arrayOf(DataTypes.number), 64 | 65 | // 对象的所有属性值都必须是指定的类型 66 | optionalObjectOf: DataTypes.objectOf(DataTypes.number), 67 | 68 | // 具有特定形状的对象 69 | optionalObjectWithShape: DataTypes.shape({ 70 | color: DataTypes.string, 71 | fontSize: DataTypes.number 72 | }), 73 | 74 | // 以上所有校验器都拥有 `isRequired` 方法,来确保此数据必须被提供 75 | requiredFunc: DataTypes.func.isRequired, 76 | requiredObject: DataTypes.shape({ 77 | color: DataTypes.string 78 | }).isRequired, 79 | 80 | // 一个必须的但可以是任何类型的数据 81 | requiredAny: DataTypes.any.isRequired, 82 | 83 | // 你也可指定一个自定义的校验器。 84 | // 如果校验失败,它应该丢出一个异常。 85 | customProp: function (props, propName, componentName) { 86 | if (!/matchme/.test(props[propName])) { 87 | throw new Error( 88 | 'Invalid prop `' + propName + '` supplied to' + 89 | ' `' + componentName + '`. Validation failed.' 90 | ); 91 | } 92 | }, 93 | 94 | // 你也可以给 `arrayOf` 和 `objectOf` 提供一个自定义校验器。 95 | // 如果校验失败,那么应该当抛出一个异常。 96 | // 对于数组或者对象中的每个元素都会调用校验器进行校验。 97 | // 第一个参数是这个数组或者对象,第二个参数是元素的 key。 98 | customArrayProp: DataTypes.arrayOf(function (dataValue, key, componentName, dataFullName) { 99 | if (!/matchme/.test(dataValue[key])) { 100 | throw new Error( 101 | 'Invalid prop `' + dataFullName + '` supplied to' + 102 | ' `' + componentName + '`. Validation failed.' 103 | ); 104 | } 105 | }) 106 | 107 | }); 108 | ``` 109 | -------------------------------------------------------------------------------- /themes/san/source/img/7.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /themes/san/layout/example.ejs: -------------------------------------------------------------------------------- 1 |
2 |
<%= __('example.start') %>
3 |
<%= __('example.start_description') %>
4 | 5 | 6 |
Todos (AMD)
7 |
<%= __('example.todos_description') %>
8 | 9 | 10 |
Todos (ESNext)
11 |
<%= __('example.todos_esnext_description') %>
12 | 13 | 14 |
Todos (<%= __('example.single_way_flow') %>)
15 |
<%= __('example.todos_store_description') %>
16 | 17 |
<%= __('example.online') %>
18 |

See the Pen san-bindx by errorrik (@errorrik) on CodePen.

19 |
20 |

See the Pen san-list by errorrik (@errorrik) on CodePen.

21 |
22 |

See the Pen san-checkbox by errorrik (@errorrik) on CodePen.

23 |
24 |

See the Pen san-radio by errorrik (@errorrik) on CodePen.

25 |
26 |

See the Pen san-slot by errorrik (@errorrik) on CodePen.

27 |
28 |
-------------------------------------------------------------------------------- /themes/san/source/js/layout_control.js: -------------------------------------------------------------------------------- 1 | 2 | (function(){ 3 | // 控制toc-wrap吸顶 4 | window.addEventListener('scroll', scrollBar); 5 | 6 | function scrollBar() { 7 | var scroll = document.body.scrollTop || document.documentElement.scrollTop; 8 | var toc = document.getElementsByClassName('toc-wrap')[0]; 9 | 10 | if (scroll >= 70) { 11 | toc.className = 'toc-wrap toc-wrap-fixed'; 12 | 13 | } 14 | else { 15 | toc.className = 'toc-wrap'; 16 | } 17 | }; 18 | 19 | // 移动端左侧目录 20 | var isX = false; 21 | 22 | document.getElementById('mobile-nav-toggle').addEventListener('click', function () { 23 | var toggle= document.getElementsByClassName('mobile-nav-toggle-bar'); 24 | var dimmer= document.getElementsByClassName('mobile-nav-dimmer')[0]; 25 | var bodyinner= document.getElementsByClassName('body-inner')[0]; 26 | var mobilenav= document.getElementsByClassName('mobile-nav')[0]; 27 | 28 | if (!isX) { 29 | for(var i = 0; i < toggle.length; i++){ 30 | toggle[i].className='mobile-nav-toggle-bar mobile-nav-toggle-bar-on'; 31 | dimmer.className='mobile-nav-dimmer mobile-nav-dimmer-on'; 32 | bodyinner.className='body-inner body-inner-on'; 33 | mobilenav.className='mobile-nav mobile-nav-on'; 34 | } 35 | 36 | isX = true; 37 | } 38 | else { 39 | for(var j = 0; j < toggle.length; j++){ 40 | toggle[j].className='mobile-nav-toggle-bar'; 41 | dimmer.className='mobile-nav-dimmer'; 42 | bodyinner.className='body-inner'; 43 | mobilenav.className='mobile-nav'; 44 | } 45 | 46 | isX = false; 47 | } 48 | }) 49 | 50 | //移动端移除placeholder 51 | function moveplaceholder(){ 52 | var width = document.body.clientWidth; 53 | var input = document.getElementById('san-query-input'); 54 | if(width<=768){ 55 | input.placeholder=""; 56 | } 57 | else{ 58 | input.placeholder="Search"; 59 | } 60 | }; 61 | moveplaceholder(); 62 | 63 | window.addEventListener('resize',moveplaceholder); 64 | 65 | 66 | //控制toc-wrap变色 67 | 68 | window.addEventListener('scroll', changecolor); 69 | 70 | function changecolor() { 71 | var tocmenu =document.getElementsByClassName('toc-level-3'); 72 | var h3 =document.getElementsByTagName('h3'); 73 | var bodytop = document.body.scrollTop || document.documentElement.scrollTop; 74 | var tocmenubig =document.getElementsByClassName('toc-level-2'); 75 | var h2 =document.getElementsByTagName('h2'); 76 | for(var i = 0; i < h2.length; i++){ 77 | var h2top = h2[i].offsetTop 78 | + document.getElementsByClassName('article-entry')[0].offsetTop 79 | + document.getElementsByClassName('article-inner')[0].offsetTop 80 | -bodytop; 81 | 82 | 83 | if (h2top<=180){ 84 | for(var j = 0; j < tocmenubig.length; j++){ 85 | tocmenubig[j].className='toc-level-2'; 86 | } 87 | tocmenubig[i].className='toc-level-2 toc-level-2-on'; 88 | } 89 | } 90 | 91 | for(var i = 0; i < h3.length; i++){ 92 | var h3top = h3[i].offsetTop 93 | + document.getElementsByClassName('article-entry')[0].offsetTop 94 | + document.getElementsByClassName('article-inner')[0].offsetTop 95 | -bodytop; 96 | 97 | 98 | if (h3top<=110){ 99 | for(var j = 0; j < tocmenu.length; j++){ 100 | tocmenu[j].className='toc-level-3'; 101 | } 102 | tocmenu[i].className='toc-level-3 toc-level-3-on'; 103 | } 104 | else{ 105 | tocmenu[i].className='toc-level-3'; 106 | } 107 | } 108 | 109 | 110 | } 111 | 112 | 113 | 114 | })(); 115 | 116 | 117 | -------------------------------------------------------------------------------- /themes/san/languages/default.yml: -------------------------------------------------------------------------------- 1 | index: 2 | nav: 3 | features: 特性 4 | resources: 资料 5 | facilities: 周边 6 | headernav: 7 | tutorial: 教程 8 | practice: 指南 9 | example: 示例 10 | api: API 11 | home: HOME 12 | feature: 13 | tpl: HTML模版 14 | tpl_description: 声明式的模板,在编写视图时就像是在写一个普通的页面,更符合 HTML 开发人员的习惯。 15 | data_driven: 数据驱动 16 | data_driven_description: 修改数据,视图引擎会根据绑定关系自动刷新视图,从此摆脱手工调用 DOM API 的繁琐与可能的遗漏。 17 | component: 组件化 18 | component_description: 组件是数据、逻辑与视图的聚合体。通过组件,我们封装独立的功能区块,小到输入组合,大到一个页面。 19 | view: 高性能视图 20 | view_description: 通过修改数据的方法,视图引擎能够直接刷新需要变更的视图区域,无需进行任何检测,性能更高。 21 | reverse: 组件反解 22 | reverse_description: 为首屏时间优化,服务端通常直接输出HTML。我们能从现有的元素中反向解析出组件,并构建绑定关系。 23 | size: 体积小巧 24 | size_description: 小于15k (gzipped) 的体积,无需担心对页面下载带来负担。体积强迫症患者的福音。 25 | compatibility: 良好的兼容性 26 | compatibility_description: 通过方法修改数据的另一好处是,可以获得更好的浏览器兼容性。毕竟有时我们产品的受众用户有点死板。 27 | module: 模块管理自由 28 | module_description: 项目中可以任意选择 ESNext Module 或 AMD 管理模块。当然,如果你想要用全局变量也是支持的。 29 | use: 引用方便 30 | use_description: 支持多种引用方式:NPM、GitHub、下载、HTTP 与 HTTPS CDN,让开发和线上引用更便利。 31 | resource: 32 | description: 这里有一些教程、文档或示例,可以帮助你学习和了解 San 33 | tutorial_description: 教程是入门的捷径,请从这里开始了解San。 34 | practice_description: 我们正在编写指南手册,以指导各种应用场景下怎么使用San。 35 | example_description: 这里展示了一些简单例子,以及在实际项目如何使用San。 36 | api: 组件 API 37 | api_description: 当你想不起来组件的接口时,请查阅这里。 38 | facility: 39 | description: 一些工具和库能够帮助你更快、更便捷地搭建自己的应用 40 | router_description: 支持hash和html5模式的router,单页或同构的Web应用通常需要它。 41 | store_description: 应用状态管理套件,其理念是类似flux的单向流。 42 | update_description: Immutable的对象更新库,和san-store配合进行应用状态数据更新。 43 | devtool_description: 基于Chrome扩展的开发者工具。 44 | article: 45 | edit: 编辑本文 46 | gotop: 回到顶部 47 | recommend: 48 | tutorial: 教程 49 | tutorial_setup: 安装 50 | tutorial_background: 背景 51 | tutorial_start: 开始 52 | tutorial_template: 模板 53 | tutorial_data_method: 数据操作 54 | tutorial_data_checking: 数据校验 55 | tutorial_style: 样式 56 | tutorial_if: 条件 57 | tutorial_for: 循环 58 | tutorial_event: 事件处理 59 | tutorial_form: 表单 60 | tutorial_slot: 插槽 61 | tutorial_transition: 过渡 62 | tutorial_component: 组件 63 | tutorial_reverse: 组件反解 64 | tutorial_ssr: 服务端渲染 65 | more: 了解更多 66 | more_component_api: 组件 API 67 | more_main_api: 主模块 API 68 | more_dist_files: 发布版本说明 69 | more_changelog: 版本更新日志 70 | facilities: 周边 71 | example: 示例 72 | example_start: 一些小示例 73 | example_single_way_flow: 单向流 74 | example: 75 | start: 小示例 76 | start_description: 一些小的示例,主要是展示 San 一些小特性的用法。 77 | todos_description: 一个 Todos 应用的例子,包含列表、列表内操作、表单提交、浮出层、交互组件等应用开发中常见的场景。 78 | todos_esnext_description: 上面 Todos 应用的 ESNext 版本。其使用了webpack + babel,更符合流行的应用开发习惯。 79 | single_way_flow: 单向流 80 | todos_store_description: 上面 Todos 应用的单向流版本。其使用了 san-store。 81 | online: 在线用法小示例 82 | practice: 83 | data: 84 | title: 数据 85 | 0: 什么东西可以保存在 data 里? 86 | 1: 什么东西不要保存在 data 里? 87 | 2: data bind 时的 auto camel 88 | tpl: 89 | title: 视图模板 90 | 0: 如何遍历一个对象? 91 | 1: 数组深层更新如何触发视图更新? 92 | 2: 如何实现元素的显示/隐藏? 93 | msg: 94 | title: 组件间通信 95 | 0: 父组件如何更新子组件? 96 | 1: 子组件如何通知父组件? 97 | 2: 子组件与更高层的组件如何通信? 98 | 3: 动态子组件如何传递消息给父组件? 99 | component: 100 | title: 组件管理 101 | 0: 我们可以操作 DOM 吗? 102 | 1: 如何处理绝对定位组件的 DOM? 103 | route: 104 | title: 路由管理 105 | 0: 如何使用 san-router 建立一个单页应用的后台系统? 106 | state: 107 | title: 应用状态管理 108 | 0: 如何使用 san-store 实现后台系统的状态管理? 109 | faq: 110 | 0: Q&A集锦 -------------------------------------------------------------------------------- /themes/san/source/js/example.json: -------------------------------------------------------------------------------- 1 | {"v":"4.12.2","fr":60,"ip":0,"op":51,"w":128,"h":128,"nm":"合成 3","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“example”轮廓 3","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":0,"s":[64,64,0],"e":[64,56,0],"to":[0,-1.33333337306976,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":20,"s":[64,56,0],"e":[64,64,0],"to":[0,0,0],"ti":[0,-1.33333337306976,0]},{"t":40}],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.252,-1.126],[0,0],[0.988,-1.976],[0.774,-0.387],[0,0],[2.252,1.126],[0,0],[-0.988,1.976],[-0.774,0.387],[0,0]],"o":[[0,0],[1.976,0.988],[-0.387,0.774],[0,0],[-2.252,1.126],[0,0],[-1.976,-0.988],[0.387,-0.774],[0,0],[2.252,-1.126]],"v":[[67.578,17.789],[112.845,40.422],[114.634,45.789],[112.845,47.578],[67.578,70.211],[60.423,70.211],[15.156,47.578],[13.367,42.211],[15.156,40.422],[60.423,17.789]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"“example”轮廓 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.51,"y":1},"o":{"x":0.4,"y":0},"n":"0p51_1_0p4_0","t":5,"s":[64,64,0],"e":[64,56,0],"to":[0,-1.33333337306976,0],"ti":[0,0,0]},{"i":{"x":0.51,"y":1},"o":{"x":0.4,"y":0},"n":"0p51_1_0p4_0","t":25,"s":[64,56,0],"e":[64,64,0],"to":[0,0,0],"ti":[0,-1.33333337306976,0]},{"t":45}],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.252,-1.126],[0,0],[0.988,-1.976],[0.774,-0.387],[0,0],[2.252,1.126],[0,0],[-0.988,1.976],[-0.774,0.387],[0,0]],"o":[[0,0],[1.976,0.988],[-0.387,0.774],[0,0],[-2.252,1.126],[0,0],[-1.976,-0.988],[0.387,-0.774],[0,0],[2.252,-1.126]],"v":[[67.578,37.789],[112.845,60.422],[114.634,65.789],[112.845,67.578],[67.578,90.211],[60.423,90.211],[15.156,67.578],[13.367,62.211],[15.156,60.422],[60.423,37.789]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":60,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"“example”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":10,"s":[64,64,0],"e":[64,56,0],"to":[0,-1.33333337306976,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":30,"s":[64,56,0],"e":[64,64,0],"to":[0,0,0],"ti":[0,-1.33333337306976,0]},{"t":50}],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-2.252,-1.126],[0,0],[0.988,-1.976],[0.774,-0.387],[0,0],[2.252,1.126],[0,0],[-0.988,1.976],[-0.774,0.387],[0,0]],"o":[[0,0],[1.976,0.988],[-0.387,0.774],[0,0],[-2.252,1.126],[0,0],[-1.976,-0.988],[0.387,-0.774],[0,0],[2.252,-1.126]],"v":[[67.578,57.789],[112.845,80.422],[114.634,85.789],[112.845,87.578],[67.578,110.211],[60.423,110.211],[15.156,87.578],[13.367,82.211],[15.156,80.422],[60.423,57.789]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":60,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0}]} -------------------------------------------------------------------------------- /source/_posts/tutorial/setup.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 安装 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 你可以通过任何喜欢或者习惯的方式安装和使用 San。 8 | 9 | 10 | 下载 11 | ----- 12 | 13 | ### 直接下载 14 | 15 | 从 [下载页面](https://github.com/baidu/san/releases) 可以获得最新以及过往版本的下载地址。 16 | 17 | ### CDN 18 | 19 | 通过 unpkg,你可以无需下载,直接引用。 20 | 21 | 开发版本: 22 | 23 | ```html 24 | 25 | ``` 26 | 27 | 生产版本: 28 | 29 | ```html 30 | 31 | ``` 32 | 33 | > 建议在开发环境不要用生产版本,开发版本提供了有助于开发的错误提示和警告! 34 | 35 | ### NPM 36 | 37 | 在使用 san 来构建大型应用时我们推荐使用 NPM 来安装。通过它能够方便的管理依赖包,以及和社区的各种开发构建工具良好配合,构建你的应用程序。 38 | 39 | ```shell 40 | # 安装最新版本 41 | $ npm install san 42 | ``` 43 | 44 | 使用 45 | ----- 46 | 47 | 48 | ### script 49 | 50 | 在页面上通过 script 标签引用需要的文件是常用的方式。可以引用下载下来的 San,也可以通过 CDN 引用。 51 | 52 | 53 | ```html 54 | 55 | 56 | 57 | 58 | 59 | ``` 60 | 61 | 注意:在引用时, 62 | 63 | - 如果页面上没有 AMD 环境,将会在页面上注册全局变量 `san` 64 | - 如果页面上有 AMD 环境,将会注册为模块 `san` 65 | 66 | 67 | ### AMD 68 | 69 | 将 San 下载下来后,通过 AMD 的方式引用 src 目录下的 main.js,可以获得灵活的模块名和整体编译的好处。但是你可能需要先配置好 packages 或 paths 项。 70 | 71 | ```js 72 | require.config({ 73 | packages: [ 74 | { 75 | name: 'san', 76 | location: 'san-path/dist/san' 77 | } 78 | ] 79 | }); 80 | ``` 81 | 82 | 在[这个例子](https://github.com/baidu/san/tree/master/example/todos-amd)里,我们可以看到一个通过 AMD 管理模块的项目是怎么引用 San 的。 83 | 84 | ### ESNext 85 | 86 | 在支持 ESNext 的环境中,可以直接引用 87 | 88 | ``` 89 | import san from 'san'; 90 | ``` 91 | 92 | ### San component 93 | 94 | 一个语法如下的 `.san` 文件,就是一个 `San component` 95 | 96 | ```html 97 | 100 | 101 | 110 | 111 | 116 | ``` 117 | 118 | 在 `webpack` 中可以使用 [san-loader](https://github.com/ecomfe/san-loader) 来加载 `.san` 文件 119 | 120 | 在 [这个例子](https://github.com/baidu/san/tree/master/example/todos-esnext) 里, 121 | 我们可以看到如何使用 `San component` 构建一个应用 122 | 123 | 开发版本 VS 生产版本 124 | ---------- 125 | 126 | 在开发中,我们推荐使用 `san.dev.js`(位于 `san/dist/san.dev.js`)。`san.dev.js` 提供了包括 [数据校验](/san/tutorial/data-checking/) 等辅助开发功能。这些辅助开发功能可以帮助你在更轻松、快速地定位和解决问题。 127 | 128 | 但出于性能考虑,正式的生产环境上需要移除了这些辅助开发功能。在 san 的发布包中提供了构建好的生产版本给大家使用,即 `san.js`(位于 `san/dist/san.js`)。你应当在构建应用的生产版本时使用它。 129 | 130 | 如果你使用 webpack 进行开发和构建 ,那么你可以通过在 webpack 配置添加 `resolve.alias` 再配合指定 `NODE_ENV` 来解决: 131 | 132 | ```js 133 | { 134 | module: { 135 | loaders: [ 136 | { 137 | test: /\.san$/, 138 | loader: 'san-loader' 139 | } 140 | ] 141 | }, 142 | resolve: { 143 | alias: { 144 | san: process.env.NODE_ENV === 'production' 145 | ? 'san/dist/san.js' 146 | : 'san/dist/san.dev.js' 147 | } 148 | } 149 | } 150 | ``` 151 | 152 | 最后,你可以通过添加两个 npm scripts 来使用不同的 webpack 配置: 153 | 154 | ```js 155 | { 156 | "name": "my-san-app", 157 | "scripts": { 158 | "dev": "NODE_ENV=development webpack-dev-server --config webpack.config.js", 159 | "build": "NODE_ENV=production webpack --config webpack.config.js" 160 | } 161 | } 162 | ``` 163 | 164 | 开始开发: 165 | 166 | ```sh 167 | npm run dev 168 | ``` 169 | 170 | 开始构建: 171 | 172 | ```sh 173 | npm run build 174 | ``` 175 | -------------------------------------------------------------------------------- /themes/san/source/img/b_design.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | b_design 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /source/_posts/tutorial/event.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 事件处理 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 事件是开发中最常用的行为管理方式。通过 **on-** 前缀,可以将事件的处理绑定到组件的方法上。 8 | 9 | `提示`:在 San 中,无论是 DOM 事件还是组件的自定义事件,都通过 **on-** 前缀绑定,没有语法区分。 10 | 11 | 12 | DOM 事件 13 | ------- 14 | 15 | **on- + 事件名** 将 DOM 元素的事件绑定到组件方法上。当 DOM 事件触发时,组件方法将被调用,this 指向组件实例。下面的例子中,当按钮被点击时,组件的 submit 方法被调用。 16 | 17 | 18 | ```javascript 19 | san.defineComponent({ 20 | template: '...', 21 | 22 | submit: function () { 23 | var title = this.data.get('title'); 24 | if (!title) { 25 | return; 26 | } 27 | 28 | sendData({title: title}); 29 | } 30 | }); 31 | ``` 32 | 33 | 34 | 绑定事件时,可以指定参数,引用当前渲染环境中的数据。参数可以是任何类型的[表达式](../template/#表达式)。 35 | 36 | ```html 37 | 38 | 45 | ``` 46 | 47 | ```javascript 48 | // Component 49 | san.defineComponent({ 50 | rmTodo: function (todo) { 51 | service.rmTodo(todo.id); 52 | this.data.remove('todos', todo); 53 | } 54 | }); 55 | ``` 56 | 57 | 58 | 指定参数时,**$event** 是 San 保留的一个特殊变量,指定 $event 将引用到 DOM Event 对象。从而你可以拿到事件触发的 DOM 对象、鼠标事件的鼠标位置等事件信息。 59 | 60 | ```javascript 61 | san.defineComponent({ 62 | template: '', 63 | 64 | clicker: function (e) { 65 | alert(e.target.tagName); // BUTTON 66 | } 67 | }); 68 | ``` 69 | 70 | 71 | 72 | 73 | 自定义事件 74 | -------- 75 | 76 | 在组件上通过 **on-** 前缀,可以绑定组件的自定义事件。 77 | 78 | 79 | 下面的例子中,MyComponent 为 Label 组件绑定了 done 事件的处理方法。 80 | 81 | ```javascript 82 | var MyComponent = san.defineComponent({ 83 | components: { 84 | 'ui-label': Label 85 | }, 86 | 87 | template: '
', 88 | 89 | labelDone: function (doneMsg) { 90 | alert(doneMsg); 91 | } 92 | }); 93 | ``` 94 | 95 | San 的组件体系提供了事件功能,Label 直接通过调用 fire 方法就能方便地派发一个事件。 96 | 97 | ```javascript 98 | var Label = san.defineComponent({ 99 | template: '', 100 | 101 | attached: function () { 102 | this.fire('done', this.data.get('text') + ' done'); 103 | } 104 | }); 105 | ``` 106 | 107 | 108 | 修饰符 109 | -------- 110 | 111 | ### capture 112 | 113 | `版本`:>= 3.3.0 114 | 115 | 在元素的事件声明中使用 capture 修饰符,事件将被绑定到捕获阶段。 116 | 117 | ```javascript 118 | var MyComponent = san.defineComponent({ 119 | template: '' 120 | + '
' 121 | + '' 122 | + '
', 123 | 124 | mainClick: function (title) { 125 | alert('Main'); 126 | }, 127 | 128 | btnClick: function (title) { 129 | alert('Button'); 130 | } 131 | }); 132 | ``` 133 | 134 | `注意`:只有在支持 **addEventListener** 的浏览器环境支持此功能,老旧 IE 上使用 capture 修饰符将没有效果。 135 | 136 | ### native 137 | 138 | `版本`:>= 3.3.0 139 | 140 | 141 | 在组件的事件声明中使用 native 修饰符,事件将被绑定到组件根元素的 DOM 事件。 142 | 143 | ```javascript 144 | var Button = san.defineComponent({ 145 | template: '' 146 | }); 147 | 148 | var MyComponent = san.defineComponent({ 149 | components: { 150 | 'ui-button': Button 151 | }, 152 | 153 | template: '
{{title}}
', 154 | 155 | clicker: function (title) { 156 | alert(title); 157 | } 158 | }); 159 | ``` 160 | 161 | 有时候组件封装了一些基础结构和样式,同时希望点击、触摸等 DOM 事件由外部使用方处理。如果组件需要 fire 每个根元素 DOM 事件是很麻烦并且难以维护的。native 修饰符解决了这个问题。 162 | 163 | 164 | -------------------------------------------------------------------------------- /themes/san/source/css/code.css: -------------------------------------------------------------------------------- 1 | article .entry .gist { 2 | background: #eee; 3 | border: 1px solid color-border; 4 | margin-top: 15px; 5 | padding: 7px 15px; 6 | border-radius: 2px; 7 | text-shadow: 0 0 1px #fff; 8 | line-height: 1.6; 9 | overflow: auto; 10 | color: #666; 11 | } 12 | article .entry .gist .gist-file { 13 | border: none; 14 | font-family: inherit; 15 | margin: 0; 16 | font-size: 0.9em; 17 | } 18 | article .entry .gist .gist-file .gist-data { 19 | background: none; 20 | border-bottom: none; 21 | } 22 | article .entry .gist .gist-file .gist-data pre { 23 | padding: 0 !important; 24 | font-family: font-mono; 25 | } 26 | article .entry .gist .gist-file .gist-meta { 27 | background: none; 28 | color: color-meta; 29 | margin-top: 5px; 30 | padding: 0; 31 | text-shadow: 0 0 1px #fff; 32 | font-size: 100%; 33 | } 34 | article .entry .gist .gist-file .gist-meta a { 35 | color: color-link; 36 | } 37 | article .entry .gist .gist-file .gist-meta a:visited { 38 | color: color-link; 39 | } 40 | article figure.highlight { 41 | background: #f2f4f6; 42 | margin: 15px 0 0; 43 | border-radius: 2px; 44 | line-height: 1.6; 45 | overflow: auto; 46 | position: relative; 47 | font-size: 0.9em; 48 | } 49 | article figure.highlight figcaption { 50 | color: color-meta; 51 | margin-bottom: 5px; 52 | text-shadow: 0 0 1px #fff; 53 | } 54 | article figure.highlight figcaption a { 55 | position: absolute; 56 | right: 15px; 57 | } 58 | article figure.highlight pre { 59 | border: none; 60 | padding: 0; 61 | margin: 0; 62 | } 63 | article figure.highlight table { 64 | margin: 0; 65 | border-spacing: 0; 66 | } 67 | article figure.highlight tr { 68 | border: 0; 69 | background: none; 70 | } 71 | article figure.highlight .gutter { 72 | display: none; 73 | } 74 | article figure.highlight .code { 75 | padding: 1.2em 1.4em; 76 | color: #666; 77 | border: 0; 78 | } 79 | article figure.highlight .line { 80 | height: 20px; 81 | } 82 | article pre{ 83 | background-color:#f2f4f6; 84 | color: #555555; 85 | } 86 | article pre .comment, 87 | article pre .template_comment, 88 | article pre .diff .header, 89 | article pre .doctype, 90 | article pre .pi, 91 | article pre .lisp .string, 92 | article pre .javadoc { 93 | color: #93a1a1; 94 | font-style: italic; 95 | } 96 | article pre .keyword, 97 | article pre .winutils, 98 | article pre .method, 99 | article pre .addition, 100 | article pre .css .tag, 101 | article pre .request, 102 | article pre .status, 103 | article pre .nginx .title { 104 | color: #859900; 105 | } 106 | article pre .number, 107 | article pre .command, 108 | article pre .string, 109 | article pre .tag .value, 110 | article pre .phpdoc, 111 | article pre .tex .formula, 112 | article pre .regexp, 113 | article pre .hexcolor { 114 | color: #2aa198; 115 | } 116 | article pre .title, 117 | article pre .localvars, 118 | article pre .chunk, 119 | article pre .decorator, 120 | article pre .built_in, 121 | article pre .identifier, 122 | article pre .vhdl, 123 | article pre .literal, 124 | article pre .id { 125 | color: #268bd2; 126 | } 127 | article pre .attribute, 128 | article pre .variable, 129 | article pre .lisp .body, 130 | article pre .smalltalk .number, 131 | article pre .constant, 132 | article pre .class .title, 133 | article pre .parent, 134 | article pre .haskell .type { 135 | color: #b58900; 136 | } 137 | article pre .preprocessor, 138 | article pre .preprocessor .keyword, 139 | article pre .shebang, 140 | article pre .symbol, 141 | article pre .symbol .string, 142 | article pre .diff .change, 143 | article pre .special, 144 | article pre .attr_selector, 145 | article pre .important, 146 | article pre .subst, 147 | article pre .cdata, 148 | article pre .clojure .title { 149 | color: #cb4b16; 150 | } 151 | article pre .deletion { 152 | color: #dc322f; 153 | } -------------------------------------------------------------------------------- /themes/san/layout/practice.ejs: -------------------------------------------------------------------------------- 1 | 44 | 45 |
46 | 47 |
48 |
<%= __('practice.data.title') %>
49 |
<%= __('practice.data.0') %>
50 |
<%= __('practice.data.1') %>
51 |
<%= __('practice.data.2') %>
52 | 53 |
<%= __('practice.tpl.title') %>
54 |
<%= __('practice.tpl.0') %>
55 |
<%= __('practice.tpl.1') %>
56 |
<%= __('practice.tpl.2') %>
57 | 58 |
<%= __('practice.msg.title') %>
59 |
<%= __('practice.msg.0') %>
60 |
<%= __('practice.msg.1') %>
61 |
<%= __('practice.msg.2') %>
62 |
<%= __('practice.msg.3') %>
63 | 64 |
<%= __('practice.component.title') %>
65 |
<%= __('practice.component.0') %>
66 | 67 | 68 |
<%= __('practice.route.title') %>
69 |
<%= __('practice.route.0') %>
70 | 71 |
<%= __('practice.state.title') %>
72 |
<%= __('practice.state.0') %>
73 | 74 | 77 |
FAQ
78 |
<%= __('practice.faq.0') %>
79 |
80 |
81 | 82 | 107 | -------------------------------------------------------------------------------- /source/_posts/en/tutorial/start.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Start 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | San is an MVVM component-based framework. Its compact size (< 15K), good compatibility (supports IE6), and excellent performance make it a reliable solution for implementing responsive user interfaces. 8 | 9 | San declaratively renders data to the DOM using an HTML-based template syntax. In the process, San compiles string-based templates to [ANode](https://github.com/baidu/san/blob/master/doc/anode.md), builds UI view instantly by high-performance view engine. Apart from raw HTML features, all San templates implement HTML-data binding, and usual syntaxes such as branching, loop, etc. Combined with the reactivity system(two-way binding and [ANode](https://github.com/baidu/san/blob/master/doc/anode.md)), San is able to intelligently figure out the minimal number of components to re-render asynchronously and apply the minimal amount of DOM manipulations when the app state changes. 10 | 11 | Component, the basic unit of San, is an independent unit of data, logic, and view. From a page perspective, a component is an extension of an HTML element; from a functional mode perspective, a component is a ViewModel. San components provide a complete lifecycle, which is part of the Web Components Spec. Also, San components can be tree-nested and communicated with each other via cross-component data flow. Therefore, San's component mechanism can effectively support the componentization requirements of business development. 12 | 13 | Based on the [component reversion](https://baidu.github.io/san/tutorial/reverse/), San provides server side rendering features through which we can create SEO friendly apps with faster initial page rendering and other pros compared with pure client side rendering. Meanwhile, the ecosystem of San offer a series of modern tooling and supporting libraries. In short, San is perfectly capable of powering large-scaled maintainable Single-Page Applications. 14 | 15 | The easiest way to try out San is using some simple [examples]. Now, Here we go. 16 | 17 | Hello 18 | ------- 19 | 20 | ```javascript 21 | var MyApp = san.defineComponent({ 22 | template: '

Hello {{name}}!

', 23 | 24 | initData: function () { 25 | return { 26 | name: 'San' 27 | }; 28 | } 29 | }); 30 | 31 | 32 | var myApp = new MyApp(); 33 | myApp.attach(document.body); 34 | ``` 35 | 36 | We create our San app following steps below: 37 | 38 | 1. First, we define a San component that specifies the component's **content template** and **initial data** . 39 | 2. Initialize the component object. 40 | 3. Attach the component to a HTML element to render. 41 | 42 | 43 | `tips`:It is unfriendly for maintenance to write HTML snippets in JavaScript. We can manage it through WebPack, AMD plugin, asynchronous request, etc. The example here is for convenience. 44 | 45 | List rendering 46 | -------- 47 | 48 | ```javascript 49 | var MyApp = san.defineComponent({ 50 | template: '', 51 | 52 | attached: function () { 53 | this.data.set('list', ['san', 'er', 'esui', 'etpl', 'esl']); 54 | } 55 | }); 56 | 57 | var myApp = new MyApp(); 58 | myApp.attach(document.body); 59 | ``` 60 | 61 | We can use the **for** directive to render a list of items based on an array. Here is a usual practice: inject data in **attached** lifecycle method to re-render. For we can initiate a request to get data and update the data in **attached** after the request returns. 62 | 63 | Two-way binding 64 | -------- 65 | 66 | ```javascript 67 | var MyApp = san.defineComponent({ 68 | template: '' 69 | + '
' 70 | + '' 71 | + 'Hello {{name}}!' 72 | + '
' 73 | }); 74 | 75 | var myApp = new MyApp(); 76 | myApp.attach(document.body); 77 | ``` 78 | 79 | In this case, We use the **{= expression =}** directive to create two-way data bindings on the input element. It automatically picks the correct way to update the element based on the input. 80 | -------------------------------------------------------------------------------- /themes/san/source/js/api.json: -------------------------------------------------------------------------------- 1 | {"v":"4.12.2","fr":60,"ip":0,"op":45,"w":128,"h":128,"nm":"合成 2","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“api”轮廓 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":0,"s":[61,66.5,0],"e":[64,63.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":11,"s":[64,63.5,0],"e":[56,71.5,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45}],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[10.817,-10.817],[0,0],[1.953,1.952],[0,0],[-1.953,1.953],[0,0],[-11.689,-8.391],[-0.097,0.096],[0,0],[-1.562,-1.562],[1.562,-1.562],[0,0]],"o":[[0,0],[-1.952,1.952],[0,0],[-1.953,-1.952],[0,0],[10.34,-10.341],[0.085,-0.104],[0,0],[1.562,-1.562],[1.562,1.562],[0,0],[9.57,11.868]],"v":[[105.115,67.885],[98.396,74.604],[91.326,74.604],[55.971,39.248],[55.971,32.177],[62.689,25.46],[100.954,22.578],[101.226,22.278],[106.883,16.621],[112.54,16.621],[112.54,22.278],[106.883,27.934]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":60,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"“api”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":1,"k":[{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":0,"s":[64.5,63.5,0],"e":[61.5,66.5,0],"to":[0,0,0],"ti":[0,0,0]},{"i":{"x":0.6,"y":1},"o":{"x":0.4,"y":0},"n":"0p6_1_0p4_0","t":11,"s":[61.5,66.5,0],"e":[72,56,0],"to":[0,0,0],"ti":[0,0,0]},{"t":45}],"ix":2},"a":{"a":0,"k":[64,64,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"hasMask":true,"masksProperties":[{"inv":false,"mode":"a","pt":{"a":1,"k":[{"i":{"x":0.833,"y":0.833},"o":{"x":0.167,"y":0.167},"n":"0p833_0p833_0p167_0p167","t":0,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[33.909,22.597],[-8.091,93.751],[32.977,136.751],[106.977,93.597]],"c":true}],"e":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[33.909,22.597],[-8.091,93.751],[32.977,136.751],[106.977,93.597]],"c":true}]},{"i":{"x":0.6,"y":1},"o":{"x":0.167,"y":0.167},"n":"0p6_1_0p167_0p167","t":22,"s":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[33.909,22.597],[-8.091,93.751],[32.977,136.751],[106.977,93.597]],"c":true}],"e":[{"i":[[0,0],[0,0],[0,0],[0,0]],"o":[[0,0],[0,0],[0,0],[0,0]],"v":[[17.393,34.144],[-8.091,93.751],[32.977,136.751],[90.461,105.894]],"c":true}]},{"t":45}],"ix":1},"o":{"a":0,"k":100,"ix":3},"x":{"a":0,"k":0,"ix":4},"nm":"蒙版 1"}],"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-1.562,-1.562],[1.562,-1.562],[0,0],[0,0],[0,0]],"o":[[1.562,1.562],[0,0],[0,0],[0,0],[1.562,-1.562]],"v":[[79.305,72.482],[79.305,78.139],[70.82,86.624],[65.163,80.967],[73.648,72.482]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-1.562,-1.562],[1.562,-1.562],[0,0],[0,0],[0,0]],"o":[[1.562,1.562],[0,0],[0,0],[0,0],[1.562,-1.562]],"v":[[58.092,51.269],[58.092,56.926],[49.607,65.411],[43.95,59.754],[52.435,51.269]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ind":2,"ty":"sh","ix":3,"ks":{"a":0,"k":{"i":[[3.817,-3.817],[1.562,1.562],[-1.562,1.562],[0,0],[-0.104,0.085],[-10.34,10.341],[0,0],[-1.953,-1.952],[0,0],[1.953,-1.953],[0,0],[11.767,9.675]],"o":[[-1.562,1.562],[-1.562,-1.562],[0,0],[0.097,-0.096],[-8.391,-11.689],[0,0],[1.953,-1.952],[0,0],[1.953,1.952],[0,0],[-10.817,10.817],[-0.022,0.024]],"v":[[22.736,112.08],[17.08,112.08],[17.08,106.423],[22.736,100.766],[23.037,100.494],[25.918,62.229],[32.636,55.511],[39.707,55.511],[75.062,90.867],[75.062,97.938],[68.345,104.655],[28.494,106.318]],"c":true},"ix":2},"nm":"路径 3","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0}]} -------------------------------------------------------------------------------- /source/_posts/en/tutorial/data-checking.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Data Validation 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | Validation rules can be specified for component data. san will throw the corresponding error when validation fails. It's rather useful in collaboration. 8 | 9 | Use `DataTypes` to declare validation rules: 10 | 11 | ```js 12 | import san, {DataTypes} from 'san'; 13 | 14 | let MyComponent = san.defineComponent({ 15 | 16 | dataTypes: { 17 | name: DataTypes.string 18 | } 19 | 20 | }); 21 | ``` 22 | 23 | `DataTypes` provides a series of validators to ensure the data received is valid. In the above example, a `DataTypes.string` validator is used so that san will throw an error when the value for `name` is not a `String`. 24 | 25 | **For performance considerations, `dataTypes` only get evaluated in `development` environment.** 26 | 27 | Please refer to this [link](https://github.com/baidu/san/tree/master/dist) to check out their availabilities in different san releases. 28 | 29 | ## DataTypes 30 | 31 | Following is a demo for a variety of `DataTypes` validators: 32 | 33 | ```js 34 | import san, {DataTypes} from 'san'; 35 | 36 | san.defineComponent({ 37 | 38 | // specified as JavaScript primitive types 39 | // these fields are optional by default 40 | optionalArray: DataTypes.array, 41 | optionalBool: DataTypes.bool, 42 | optionalFunc: DataTypes.func, 43 | optionalNumber: DataTypes.number, 44 | optionalObject: DataTypes.object, 45 | optionalString: DataTypes.string, 46 | optionalSymbol: DataTypes.symbol, 47 | 48 | // specified as instances of some class 49 | // it's implemented by `instanceof` 50 | optionalMessage: DataTypes.instanceOf(Message), 51 | 52 | // if values are from a fixed set, declare it as an enum type 53 | optionalEnum: DataTypes.oneOf(['News', 'Photos']), 54 | 55 | // can be specified as one of many types 56 | optionalUnion: DataTypes.oneOfType([ 57 | DataTypes.string, 58 | DataTypes.number, 59 | DataTypes.instanceOf(Message) 60 | ]), 61 | 62 | // each item of the Array must be of the specified type 63 | optionalArrayOf: DataTypes.arrayOf(DataTypes.number), 64 | 65 | // each property of the Object must have a value of the specified type 66 | optionalObjectOf: DataTypes.objectOf(DataTypes.number), 67 | 68 | // objects with a specified structure 69 | optionalObjectWithShape: DataTypes.shape({ 70 | color: DataTypes.string, 71 | fontSize: DataTypes.number 72 | }), 73 | 74 | // every validator above provides a `isRequired` method to specify the field as required 75 | requiredFunc: DataTypes.func.isRequired, 76 | requiredObject: DataTypes.shape({ 77 | color: DataTypes.string 78 | }).isRequired, 79 | 80 | // a required field of any type 81 | requiredAny: DataTypes.any.isRequired, 82 | 83 | // custom validators can be defined, simply throw an error to indicate a validation failure 84 | customProp: function (props, propName, componentName) { 85 | if (!/matchme/.test(props[propName])) { 86 | throw new Error( 87 | 'Invalid prop `' + propName + '` supplied to' + 88 | ' `' + componentName + '`. Validation failed.' 89 | ); 90 | } 91 | }, 92 | 93 | // Validators for `arrayOf` and `objectOf` can be defined. 94 | // Throw an error if validation fails. 95 | // Every item of the Array (or Object) will be validated against the custom validator. 96 | // The first argument is the Array (or Object), 97 | // The second argument is the index (or property name) of the item (or property) to be validated. 98 | customArrayProp: DataTypes.arrayOf(function (dataValue, key, componentName, dataFullName) { 99 | if (!/matchme/.test(dataValue[key])) { 100 | throw new Error( 101 | 'Invalid prop `' + dataFullName + '` supplied to' + 102 | ' `' + componentName + '`. Validation failed.' 103 | ); 104 | } 105 | }) 106 | 107 | }); 108 | ``` 109 | -------------------------------------------------------------------------------- /source/_posts/en/tutorial/form.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Form 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 8 | Forms are common user input hosting elements, and this section describes the usage of some common form elements. In MVVM, we typically apply **two-way binding** on form elements or components entered by the user. 9 | 10 | 11 | Input 12 | ----- 13 | 14 | The binding method of the input box is relatively simple, and it is sufficient to apply the two-way binding directly to the value attribute of the input box. 15 | 16 | 17 | ```html 18 | 19 | ``` 20 | 21 | 22 | checkbox 23 | ------- 24 | 25 | The common usage scenarios for checkboxes are grouping. In the component template, we bind the checked property to the component data of the same name in both directions. 26 | 27 | `Hint`: Unless you need to make a traditional form submission, you don't need to specify the checkbox's name attribute. San only uses checked as the basis for grouping. 28 | 29 | ```html 30 | 31 |
32 | 33 | 34 | 35 |
36 | ``` 37 | 38 | The data item we expect the checkbox to bind to is a **Array<string>** . When the checkbox is selected, its value is added to the bound data item; when the checkbox is unchecked, its value is removed from the bound data item. 39 | 40 | ```js 41 | // Component 42 | san.defineComponent({ 43 | // ... 44 | 45 | initData: function () { 46 | return { 47 | online: [] 48 | }; 49 | }, 50 | 51 | attached: function () { 52 | this.data.set('online', ['errorrik', 'otakustay']); 53 | } 54 | }); 55 | 56 | ``` 57 | 58 | 59 | 60 | radio 61 | ----- 62 | 63 | Similar to the checkbox, in the component template, we bind the checked attribute to the component data of the same name in the radio that needs to be grouped. 64 | 65 | `Hint`: You need to manually specify the name attribute of the group radio so that the browser can handle the mutual exclusion of the radio selection. It can be set to the same name as the bound data. 66 | 67 | ```html 68 | 69 |
70 | 71 | 72 | 73 |
74 | ``` 75 | 76 | The data item we expect radio to bind to is a **string** . When radio is selected, the bound data item value is set to the value of the selected radio's value property. 77 | 78 | ```js 79 | // Component 80 | san.defineComponent({ 81 | // ... 82 | 83 | initData: function () { 84 | return { 85 | online: 'errorrik' 86 | }; 87 | } 88 | }); 89 | ``` 90 | 91 | 92 | select 93 | ------ 94 | 95 | The select is used in a similar way to the input box, applying a two-way binding directly to the value property. 96 | 97 | ```html 98 | 99 | 104 | ``` 105 | 106 | `Hint`: In the browser, the value attribute of select does not control its selected item, and the selected item of select is controlled by the selected attribute of option. Considering the ease of development, the developer does not need to write the selected property of option, and San will refresh the selected state of select in the next view update time slice. 107 | 108 | ```js 109 | // Component 110 | san.defineComponent({ 111 | // ... 112 | 113 | initData: function () { 114 | return { 115 | online: 'errorrik' 116 | }; 117 | } 118 | }); 119 | ``` 120 | -------------------------------------------------------------------------------- /themes/san/source/js/dev.json: -------------------------------------------------------------------------------- 1 | {"v":"4.12.2","fr":60,"ip":0,"op":240,"w":128,"h":128,"nm":"合成 4","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"“b_store”轮廓 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.6],"y":[1]},"o":{"x":[0.4],"y":[0]},"n":["0p6_1_0p4_0"],"t":0,"s":[0],"e":[90]},{"t":30}],"ix":10},"p":{"a":0,"k":[100,99,0],"ix":2},"a":{"a":0,"k":[100,99,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-4.172,-1.686],[-1.686,4.172],[4.172,1.685],[1.686,-4.171]],"o":[[4.172,1.686],[1.685,-4.172],[-4.172,-1.686],[-1.685,4.172]],"v":[[96.963,106.568],[107.569,102.066],[103.067,91.461],[92.461,95.962]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[2.111,0.336],[1.746,0.706],[1.308,-1.185],[1.219,1.68],[0.736,1.733],[1.762,0.088],[-0.336,2.11],[-0.705,1.746],[1.185,1.308],[-1.68,1.218],[-1.733,0.736],[-0.088,1.762],[-2.11,-0.336],[-1.746,-0.706],[-1.308,1.185],[-1.218,-1.679],[-0.736,-1.734],[-1.763,-0.087],[0.335,-2.11],[0.705,-1.747],[-1.185,-1.309],[1.68,-1.218],[1.733,-0.735],[0.087,-1.762]],"o":[[-0.117,-1.761],[-1.747,-0.706],[-1.752,-1.225],[1.161,-1.328],[-0.736,-1.733],[-0.362,-2.043],[1.761,-0.118],[0.706,-1.746],[1.225,-1.751],[1.328,1.161],[1.733,-0.735],[2.043,-0.362],[0.118,1.761],[1.746,0.705],[1.751,1.224],[-1.161,1.329],[0.736,1.733],[0.362,2.043],[-1.762,0.118],[-0.706,1.746],[-1.224,1.751],[-1.329,-1.161],[-1.734,0.736],[-2.043,0.362]],"v":[[97.043,117.794],[94.081,113.702],[89.107,114.588],[84.635,110.191],[85.433,105.204],[81.291,102.313],[81.235,96.043],[85.327,93.08],[84.441,88.106],[88.838,83.635],[93.825,84.432],[96.716,80.291],[102.986,80.234],[105.949,84.327],[110.923,83.441],[115.394,87.837],[114.597,92.825],[118.739,95.715],[118.739,102.313],[114.703,104.949],[115.588,109.923],[111.192,114.394],[106.205,113.596],[103.314,117.738]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":60,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"“b_store”轮廓","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":1,"k":[{"i":{"x":[0.6],"y":[1]},"o":{"x":[0.4],"y":[0]},"n":["0p6_1_0p4_0"],"t":0,"s":[0],"e":[-90]},{"t":30}],"ix":10},"p":{"a":0,"k":[56.5,56.5,0],"ix":2},"a":{"a":0,"k":[56.5,56.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[-11.082,0],[0,11.082],[11.082,0],[0,-11.082]],"o":[[11.082,0],[0,-11.082],[-11.082,0],[0,11.082]],"v":[[56.001,76.066],[76.067,56],[56.001,35.934],[35.935,56]],"c":true},"ix":2},"nm":"路径 1","mn":"ADBE Vector Shape - Group","hd":false},{"ind":1,"ty":"sh","ix":2,"ks":{"a":0,"k":{"i":[[-2.179,-3.833],[4.866,-1.068],[4.184,0],[1.511,-3.634],[4.093,2.565],[3.265,3.265],[3.83,-2.165],[1.066,4.865],[0,4.173],[3.618,1.519],[-2.563,4.092],[-3.257,3.258],[2.153,3.827],[-4.863,1.069],[-4.168,0],[-1.519,3.618],[-4.094,-2.56],[-3.261,-3.261],[-3.83,2.166],[-1.071,-4.863],[0,-4.179],[-3.635,-1.511],[2.562,-4.095],[3.268,-3.268]],"o":[[-4.094,2.563],[-1.51,-3.637],[-4.181,0],[-4.864,-1.071],[2.171,-3.831],[-3.262,-3.263],[-2.56,-4.094],[3.622,-1.517],[0,-4.17],[1.07,-4.863],[3.828,2.154],[3.257,-3.257],[4.093,-2.562],[1.52,3.616],[4.171,0],[4.864,1.067],[-2.16,3.829],[3.263,3.263],[2.565,4.092],[-3.63,1.514],[0,4.182],[-1.068,4.865],[-3.833,-2.178],[-3.269,3.269]],"v":[[78.784,92.473],[65.268,97.997],[55.988,91.802],[46.711,97.992],[33.199,92.461],[31.558,80.411],[19.517,78.765],[14,65.249],[20.167,55.981],[14.008,46.716],[19.534,33.207],[31.558,31.551],[33.215,19.529],[46.725,14.005],[55.988,20.16],[65.254,14],[78.768,19.517],[80.419,31.551],[92.461,33.196],[97.992,46.706],[91.81,55.981],[98,65.259],[92.479,78.776],[80.419,80.411]],"c":true},"ix":2},"nm":"路径 2","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"mm","mm":1,"nm":"合并路径 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[1,1,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"填充 1","mn":"ADBE Vector Graphic - Fill","hd":false}],"ip":0,"op":240,"st":0,"bm":0}]} -------------------------------------------------------------------------------- /source/_posts/practice/auto-camel.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: data bind时的auto camel 3 | categories: 4 | - practice 5 | --- 6 | 7 | 在 san 组件中,data 的键值必须遵守 camelCase (驼峰式)的命名规范,不得使用 kebab-case (短横线隔开式)规范。 8 | 9 | ## 场景一 10 | 11 | 当一个父组件调用子组件并进行 data 绑定时,如果某一项属性写法使用了 kebab-case,san 会自动将其转换为 camelCase,然后传入子组件。下面的一个例子说明了这一点: 12 | 13 | ### 示例一 14 | 15 | ```javascript 16 | class Child extends san.Component { 17 | static template = ` 18 |
    19 |
  1. {{dataParent}}
  2. 20 |
  3. {{data-parent}}
  4. 21 |
22 | `; 23 | } 24 | 25 | class Parent extends san.Component { 26 | static template = ` 27 |
28 | 29 |
30 | `; 31 | 32 | static components = { 33 | 'san-child': Child 34 | }; 35 | } 36 | 37 | new Parent().attach(document.body); 38 | ``` 39 | 40 |

See the Pen vJQgWm by Ma Lingyang (@mly-zju) on CodePen.

41 | 42 | 43 | ### 分析 44 | 45 | 上面例子中,父组件调用子组件,为`data-parent`属性传入了"data from parent!"字符串。在子组件中,同时在li标签中输出`dataParent`和`data-parent`属性的值,可以看到,`dataParent`打印出的正是父组件绑定的值,作为对比,`data-parent`并没有输出我们期望的绑定值。从这个例子中可以很明显看出,对于传入的属性键值,san会自动将 kebab-case 写法转换为 camelCase。而作为对比,在原生 html 标签中,并不会有 auto-camel 的特性,我们如果传入一个自定义的 kebab-case 写法的属性,依然可以通过`dom.getAttribute('kebab-case')`来进行读取。san 的 template 与原生 html 的这一点不同值得我们注意。 46 | 47 | 在这个场景中的 auto camel 是很有迷惑性的,这个特性很容易让我们误以为在开发中,定义组件的属性键值时候我们可以随心所欲的混用 camelCase 和 kebab-case,因为反正 san 会自动帮我们转换为 camelCase 形式。那么,实际上是不是如此呢?来看场景二。 48 | 49 | ## 场景二 50 | 51 | 在场景一中,父组件为子组件绑定了一个 kebab-case 写法的属性,被自动转换为 camelCase。那么在子组件中,如果自身返回的初始 data 属性本身就是 kebab-case 类型,又会出现怎样的情况呢?我们看第二个例子: 52 | 53 | ### 示例二 54 | 55 | ```javascript 56 | class Child extends san.Component { 57 | static template = ` 58 |
    59 |
  1. {{dataSelf}}
  2. 60 |
  3. {{data-self}}
  4. 61 |
62 | `; 63 | 64 | initData() { 65 | return { 66 | 'data-self': 'data from myself!' 67 | } 68 | } 69 | } 70 | 71 | new Child().attach(document.body); 72 | ``` 73 | 74 |

See the Pen QMJpvL by Ma Lingyang (@mly-zju) on CodePen.

75 | 76 | 77 | ### 分析 78 | 79 | 在上面例子中,Child 组件初始 data 中包含一项键值为`data-self`的数据。我们将其分别以`dataSelf`和`data-self`打印到 li 标签中,可以看到,两种都没有正确打印出我们初始化的值。说明对于自身 data 属性而言,如果属性的键值不是 camelCase 的形式,san 并不会对其进行 auto camel 转换,所以我们无论以哪种方式,都无法拿到这个数据。 80 | 81 | ## 原理分析 82 | 83 | 在 san 的 compile 过程中,对 template 的解析会返回一个 ANODE 类的实例。其中 template 中绑定属性的时候,属性对象的信息会解析为 ANODE 实例中的 props 属性。对于子组件来说,会根据父组件的 aNode.props 来生成自身的 data binds。 84 | 85 | 在 san 中,非根组件做 data binds 过程中,接受父组件的 aNode.props 这一步时,会做 auto camel 处理。这就解释了上述两个例子为什么父组件 kebab 属性传入后,子组件 camel 属性表现正常,其余情况都是异常的。事实上在 san 的源码中,我们可以找到相关的处理函数: 86 | 87 | ```javascript 88 | function kebab2camel(source) { 89 | return source.replace(/-([a-z])/g, function (match, alpha) { 90 | return alpha.toUpperCase(); 91 | }); 92 | } 93 | 94 | function camelComponentBinds(binds) { 95 | var result = new IndexedList(); 96 | binds.each(function (bind) { 97 | result.push({ 98 | name: kebab2camel(bind.name), 99 | expr: bind.expr, 100 | x: bind.x, 101 | raw: bind.raw 102 | }); 103 | }); 104 | 105 | return result; 106 | } 107 | ``` 108 | 109 | 在生成子组件的绑定过程中,正是由于调用了 camelComponentBinds 这个函数,所以才有 auto camel 的特性。 110 | 111 | ## 结论 112 | 113 | san 的 auto camel 只适用于父组件调用子组件时候的数据绑定。对于一个组件自身的初始数据,如果属性为 kebab-case,我们将无法正确拿到数据。因此,在写 san 组件的过程中,无论何时,对于 data 中的属性键值,我们都应该自觉地严格遵循 camelCase 规范。 -------------------------------------------------------------------------------- /source/_posts/en/tutorial/for.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: loop 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | It is a common scenario to render a list via loop. We can render a list by applying the **s-for** directive on the element. 8 | 9 | 10 | Syntax 11 | ---- 12 | 13 | The syntax of the **s-for** directive is as follows: 14 | 15 | ``` 16 | item-identifier[, index-identifier] in expression[ trackBy accessor-expression] 17 | ``` 18 | 19 | List Rendering 20 | ---- 21 | 22 | The following code describes the use of the **s-for** directive on an element to render a list. Inside the elements rendered by the list, you can access other data on the owner component (dept in the example below). 23 | 24 | ```html 25 | 26 |
27 |
name - email
28 |
{{p.name}}({{dept}}) - {{p.email}}
29 |
30 | ``` 31 | 32 | ```js 33 | // Component 34 | san.defineComponent({ 35 | // template 36 | 37 | initData: function () { 38 | return { 39 | dept: 'ssg', 40 | persons: [ 41 | {name: 'errorrik', email: 'errorrik@gmail.com'}, 42 | {name: 'otakustay', email: 'otakustay@gmail.com'} 43 | ] 44 | }; 45 | } 46 | }); 47 | ``` 48 | 49 | Index 50 | ---- 51 | 52 | The index variable name (index in the example below) can be specified in the **s-for** directive to get the index of the list element in the list rendering process. 53 | 54 | ```html 55 | 56 |
57 |
name - email
58 |
{{index + 1}}. {{p.name}}({{dept}}) - {{p.email}}
59 |
60 | ``` 61 | 62 | ```js 63 | // Component 64 | san.defineComponent({ 65 | // template 66 | 67 | initData: function () { 68 | return { 69 | dept: 'ssg', 70 | persons: [ 71 | {name: 'errorrik', email: 'errorrik@gmail.com'}, 72 | {name: 'otakustay', email: 'otakustay@gmail.com'} 73 | ] 74 | }; 75 | } 76 | }); 77 | ``` 78 | 79 | List data operation 80 | ------- 81 | 82 | To manipulate list data such as inserting or deleting items, we use the array method provided by the component data. For details, please refer to the [Array Method] (../data-method/#Array-Method) document. 83 | 84 | 85 | Virtual element 86 | ------ 87 | 88 | As with the if directive, applying the for directive to the template element allows multiple elements to be rendered at the same time based on traversal, eliminating the need for an unnecessary parent element. 89 | 90 | 91 | ```html 92 | 93 |
94 | 98 |
99 | ``` 100 | 101 | trackBy 102 | ------ 103 | 104 | `>= 3.6.1` 105 | 106 | 107 | Specify **trackBy** in the **s-for** directive declaration. When the array is updated, San will automatically track the changes of the items and perform the corresponding insert/remove operations. **trackBy** can only declare attribute access to item-identifier. 108 | 109 | 110 | ```html 111 | 112 |
113 |
name - email
114 |
{{p.name}}({{dept}}) - {{p.email}}
115 |
116 | ``` 117 | 118 | ```js 119 | // Component 120 | san.defineComponent({ 121 | // template 122 | 123 | initData: function () { 124 | return { 125 | dept: 'ssg', 126 | persons: [ 127 | {name: 'errorrik', email: 'errorrik@gmail.com'}, 128 | {name: 'otakustay', email: 'otakustay@gmail.com'} 129 | ] 130 | }; 131 | } 132 | }); 133 | ``` 134 | 135 | 136 | TrackBy is typically used when rendering JSON data returned by the backend. Because the two JSON parse cannot make a === comparison on the list elements, the track will track the changes internally through trackBy. When combined with transitions, the animation of the change process will be more reasonable. 137 | 138 | In the following scenario, the performance of using trackBy will be worse: 139 | 140 | - All data items have changed 141 | - The order of data items before and after changes is different 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /source/_posts/en/tutorial/ssr.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Server Side Rendering 3 | categories: 4 | - tutorial 5 | --- 6 | 7 | 8 | 9 | San's server-side rendering is based on [component reversion](../reverse/). 10 | 11 | - The HTML output from the server contains markups fragment that has no effect on the view and helps the component understand the structure of the data and view. 12 | - On the browser side, the component structure is understood from the markup fragment when the component is initialized, and the component can respond correctly when the user operates. 13 | 14 | `tips`: Since the component runtime environment needs to consider various browser version as well as NodeJS, the sample code is guaranteed to be simple and no transform is required, all written in ES5. 15 | 16 | Do you need SSR? 17 | ---- 18 | 19 | We can gain some obvious benefits from server-side rendering which outputs HTML directly: 20 | 21 | - SEO friendly, HTML direct output is more beneficial to search engine understanding and understanding 22 | - Users can see the content the first time. In the developer's opinion, the first screen time is coming sooner. 23 | 24 | However, if you use server-side rendering, we will face: 25 | 26 | - Higher cost. Although we only need to develop one code of components, we need to consider its runtime is both NodeJS and browser; we need to consider rendering on the server side to compile in advance; we need to consider how the component's source code is output to the browser; we need to consider the browser compatibility of the component, whether to write old browser compatible code or write it by ESNext and then package the compile-time transform. These still bring about an increase in maintenance costs, even if not much. 27 | - User interaction time does not necessarily arrive earlier. The interaction behavior is managed by the component. Components need to traverse the DOM tree to reversing the data and structure from the current view. The speed of component-reversion is not necessarily faster than the direct rendering at the front end. 28 | 29 | Therefore, we recommend a comprehensive evaluation when using server-side rendering. Use SSR only in scenarios that must be used. Here are some scenario suggestions: 30 | 31 | - Most of the backend systems (such as CMS、MIS、DashBoard) use the Single Page Application mode. Obviously, these systems do not need to use SSR. 32 | - Functional pages, such as personal center, my collection, etc, do not require SSR. 33 | - Appear only in the App's WebView, not as an open web page, no need to use SSR. 34 | - Focus on content pages, you can use SSR. But the component manages the behavioral interaction, and no component rendering is required for the content part. Only need to perform component de-rendering in the part with interaction. 35 | 36 | Output HTML 37 | ---- 38 | 39 | ```javascript 40 | var MyComponent = san.defineComponent({ 41 | template: '{{name}}' 42 | }); 43 | 44 | var render = san.compileToRenderer(MyComponent); 45 | render({ 46 | email: 'errorrik@gmail.com', 47 | name: 'errorrik' 48 | }); 49 | // render html result: 50 | // .... 51 | ``` 52 | 53 | 54 | San provides the **compileToRenderer** method in the main package. This method takes the component's class as a parameter, then compiles, and returns a **{string}render({Object} data)** method. **render** method receives the data and returns rendered HTML string of the component. 55 | 56 | 57 | Compile the NodeJS module 58 | ---- 59 | 60 | Sometimes, we want the render method compiled by a component to be a separate NodeJS Module so that other modules can reference it. We can compile the NodeJS Module via the **compileToSource** method provided by the San main package. 61 | 62 | ```javascript 63 | var san = require('san'); 64 | var fs = require('fs'); 65 | 66 | var MyComponent = san.defineComponent({ 67 | template: '{{name}}' 68 | }); 69 | 70 | var renderSource = san.compileToSource(MyComponent); 71 | fs.writeFileSync('your-module.js', 'exports = module.exports = ' + renderSource, 'UTF-8'); 72 | ``` 73 | 74 | The **compileToSource** method takes the component's class as a parameter, then compiles, and returns the source code of the component rendered. Specifically it is `function (data) {...}` string. We only need to add `exports = module.exports = ` to the front and write it to the **.js** file to get a NodeJS Module that conforms to the CommonJS standard. 75 | -------------------------------------------------------------------------------- /source/_posts/practice/parent-to-child.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 父组件如何更新子组件? 3 | categories: 4 | - practice 5 | --- 6 | 7 | #### props 8 | 9 | 最简单的也是最常用的父组件更新子组件的方式就是父组件将数据通过**props**传给子组件,当相关的变量被更新的时候,MVVM框架会自动将数据的更新映射到视图上。 10 | 11 | 12 | ```javascript 13 | class Son extends san.Component { 14 | static template = ` 15 |
16 |

Son's name: {{firstName}}

17 |
18 | `; 19 | }; 20 | 21 | class Parent extends san.Component { 22 | static template = ` 23 |
24 | 25 | 26 |
27 | `; 28 | 29 | static components = { 30 | 'ui-son': Son 31 | }; 32 | 33 | initData() { 34 | return { 35 | firstName: 'trump' 36 | } 37 | } 38 | }; 39 | ``` 40 | 41 |

See the Pen san-parent-to-child-prop by liuchaofan (@asd123freedom) on CodePen.

42 | 43 | 44 | #### ref 45 | 更灵活的方式是通过**ref**拿到子组件的实例,通过这个子组件的实例可以手动调用`this.data.set`来更新子组件的数据,或者直接调用子组件声明时定义的成员方法。 46 | 47 | ```javascript 48 | class Son extends san.Component { 49 | static template = ` 50 |
51 |

Son's: {{firstName}}

52 |
53 | `; 54 | }; 55 | 56 | class Parent extends san.Component { 57 | static template = ` 58 |
59 | 60 | 61 | 62 |
63 | `; 64 | static components = { 65 | 'ui-son': Son 66 | }; 67 | onClick() { 68 | this.ref('son').data.set('firstName', this.data.get('firstName')); 69 | } 70 | } 71 | ``` 72 | 73 |

See the Pen san-parent-to-child-ref by liuchaofan (@asd123freedom) on CodePen.

74 | 75 | 76 | #### message 77 | 78 | 除了**ref**外,父组件在接收子组件向上传递的消息的时候,也可以拿到子组件的实例,之后的操作方式就和上面所说的一样了。 79 | 80 | ```javascript 81 | class Son extends san.Component { 82 | static template = ` 83 |
84 |

Son's name: {{firstName}}

85 | 86 |
87 | `; 88 | 89 | onClick() { 90 | this.dispatch('son-clicked'); 91 | } 92 | }; 93 | 94 | class Parent extends san.Component { 95 | static template = ` 96 |
97 | 98 | 99 |
100 | `; 101 | 102 | // 声明组件要处理的消息 103 | static messages = { 104 | 'son-clicked': function (arg) { 105 | let son = arg.target; 106 | let firstName = this.data.get('firstName'); 107 | son.data.set('firstName', firstName); 108 | } 109 | }; 110 | 111 | static components = { 112 | 'ui-son': Son 113 | }; 114 | 115 | initData() { 116 | return { 117 | firstName: 'trump' 118 | } 119 | } 120 | }; 121 | ``` 122 | 123 |

See the Pen san-parent-to-child-prop by liuchaofan (@asd123freedom) on CodePen.

124 | 125 | -------------------------------------------------------------------------------- /themes/san/source/img/6.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /themes/san/languages/en.yml: -------------------------------------------------------------------------------- 1 | index: 2 | nav: 3 | features: Features 4 | resources: Resources 5 | facilities: Facilities 6 | headernav: 7 | tutorial: Tutorial 8 | practice: Practice 9 | example: Example 10 | api: API 11 | home: HOME 12 | feature: 13 | tpl: HTML模版 14 | tpl_description: 声明式的模板,在编写视图时就像是在写一个普通的页面,更符合 HTML 开发人员的习惯。 15 | data_driven: 数据驱动 16 | data_driven_description: 修改数据,视图引擎会根据绑定关系自动刷新视图,从此摆脱手工调用 DOM API 的繁琐与可能的遗漏。 17 | component: 组件化 18 | component_description: 组件是数据、逻辑与视图的聚合体。通过组件,我们封装独立的功能区块,小到输入组合,大到一个页面。 19 | view: High Performance View 20 | view_description: By modifying the data, the view engine can directly refresh the view area that needs to be changed without any detection and better performance. 21 | reverse: Component Reversion 22 | reverse_description: Optimized for the first screen and output HTML from the server side. Reverse parse components from existing elements and build bindings. 23 | size: Small Size 24 | size_description: Volume less that 15kb (gzipped), no need to worry about the burden of page downloads. The gospel of patients with volume obsessive. 25 | compatibility: 良好的兼容性 26 | compatibility_description: 通过方法修改数据的另一好处是,可以获得更好的浏览器兼容性。毕竟有时我们产品的受众用户有点死板。 27 | module: 模块管理自由 28 | module_description: 项目中可以任意选择 ESNext Module 或 AMD 管理模块。当然,如果你想要用全局变量也是支持的。 29 | use: 引用方便 30 | use_description: 支持多种引用方式:NPM、GitHub、下载、HTTP 与 HTTPS CDN,让开发和线上引用更便利。 31 | resource: 32 | description: 这里有一些教程、文档或示例,可以帮助你学习和了解 San 33 | tutorial_description: 教程是入门的捷径,请从这里开始了解San。 34 | practice_description: 我们正在编写指南手册,以指导各种应用场景下怎么使用San。 35 | example_description: 这里展示了一些简单例子,以及在实际项目如何使用San。 36 | api: 组件 API 37 | api_description: 当你想不起来组件的接口时,请查阅这里。 38 | facility: 39 | description: Some tools and librarys can help you build your app faster and easier. 40 | router_description: Support for hash and html5 mode routes to build SPA(Single Page Application) or isomorphism web applications. 41 | store_description: Application state management suite, the idea is a one-way flow like flux. 42 | update_description: Immutable's object update library, with san-store for application state data updates. 43 | devtool_description: Developer tools based on Chrome extensions. 44 | article: 45 | edit: Edit 46 | gotop: Top 47 | recommend: 48 | tutorial: Tutorial 49 | tutorial_setup: Setup 50 | tutorial_background: Background 51 | tutorial_start: Start 52 | tutorial_template: Template 53 | tutorial_data_method: Data Manipulation 54 | tutorial_data_checking: Data Validation 55 | tutorial_style: Style 56 | tutorial_if: Conditional 57 | tutorial_for: Loop 58 | tutorial_event: Event 59 | tutorial_form: Form 60 | tutorial_slot: Slot 61 | tutorial_transition: Transition 62 | tutorial_component: Component 63 | tutorial_reverse: Component Reversion 64 | tutorial_ssr: SSR 65 | more: More 66 | more_component_api: 组件 API 67 | more_main_api: 主模块 API 68 | more_dist_files: 发布版本说明 69 | more_changelog: 版本更新日志 70 | facilities: Facilities 71 | example: 示例 72 | example_start: 一些小示例 73 | example_single_way_flow: 单向流 74 | example: 75 | start: 小示例 76 | start_description: 一些小的示例,主要是展示 San 一些小特性的用法。 77 | todos_description: 一个 Todos 应用的例子,包含列表、列表内操作、表单提交、浮出层、交互组件等应用开发中常见的场景。 78 | todos_esnext_description: 上面 Todos 应用的 ESNext 版本。其使用了webpack + babel,更符合流行的应用开发习惯。 79 | single_way_flow: 单向流 80 | todos_store_description: 上面 Todos 应用的单向流版本。其使用了 san-store。 81 | online: 在线用法小示例 82 | practice: 83 | data: 84 | title: Data 85 | 0: What Content is Suitable for the Data? 86 | 1: What Content is Not Suitable for the Data? 87 | 2: Auto camel when data bind 88 | tpl: 89 | title: View Templates 90 | 0: Object Traversal 91 | 1: How does an array deep update trigger a view update? 92 | 2: How to Implement Hide and Show? 93 | msg: 94 | title: Inter-Component Messaging 95 | 0: How to Update Child Components? 96 | 1: Child-to-Parent Messaging 97 | 2: How child components communicate with higher layer components 98 | 3: Messaging from Dynamic Child Components 99 | component: 100 | title: Component Management 101 | 0: Can we do DOM manipulation? 102 | 1: How to deal with the absolute positioning component's DOM? 103 | route: 104 | title: Router Management 105 | 0: How to use san-router to create a back-end system for a single-page application? 106 | state: 107 | title: Application State Management 108 | 0: How to use san-store to implement state management of the backend system? 109 | faq: 110 | 0: FAQ --------------------------------------------------------------------------------