├── .gitignore
├── Fluxify.js
├── README.md
└── examples
└── chat
├── .gitignore
├── css
└── chatapp.css
├── images
└── loading.png
├── index.html
├── js
├── actions
│ ├── ChatServerActionCreator.js
│ └── ChatViewActionCreator.js
├── constants
│ └── Constants.js
├── index.js
├── stores
│ ├── MessageStore.js
│ ├── ThreadStore.js
│ └── UnreadThreadStore.js
├── utils
│ ├── ChatMessageUtil.js
│ ├── Events.js
│ ├── WebAPIUtils.js
│ └── mockdata.js
└── views
│ ├── MessageComposer.js
│ ├── MessageComposer.jsx
│ ├── MessageListItem.js
│ ├── MessageListItem.jsx
│ ├── MessageSection.js
│ ├── MessageSection.jsx
│ ├── ThreadListItem.js
│ ├── ThreadListItem.jsx
│ ├── ThreadSection.js
│ ├── ThreadSection.jsx
│ ├── index.js
│ └── index.jsx
└── lib
├── Fluxify.js
├── react.js
└── sea.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 |
--------------------------------------------------------------------------------
/Fluxify.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kpaxqin@github on 15-2-6.
3 | */
4 | (function(global, factory){
5 | // CMD & CMD-like
6 | if (typeof define ==="function"){
7 | define(function(require, exports, module){
8 | factory(global, exports);
9 | })
10 | }else {
11 | factory(global, {});
12 | }
13 | })(this, function(root, Fluxify){
14 |
15 | // var Fluxify = window.Fluxify = {};
16 |
17 | var _OP = Object.prototype,
18 | _AP = Array.prototype,
19 | hasOwn = _OP.hasOwnProperty;
20 |
21 | Fluxify.utils = {
22 | /*
23 | * change {foo: null, bar: null} to {foo: "foo", bar: "bar"}
24 | * keyMirror({foo: 1}) ==> {"foo": 1}, we'll not overwrite existed value by default
25 | * keyMirror({foo: 1}, true) ==> {"foo" : "foo"}
26 | * */
27 | keyMirror: function(object, overwrite){
28 | overwrite = overwrite || false;
29 |
30 | var result = {};
31 |
32 | for (var key in object){
33 | if (!hasOwn.call(object, key)){
34 | continue;
35 | }
36 |
37 | if (object[key] != null){
38 | result[key] = overwrite ? key : object[key];
39 | }else{
40 | result[key] = key;
41 | }
42 | }
43 |
44 | return result;
45 | },
46 | /*
47 | * Object.assign polyfill from https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
48 | * */
49 | objectAssign: function(target, firstSource) {
50 | "use strict";
51 | if (target === undefined || target === null)
52 | throw new TypeError("Cannot convert first argument to object");
53 | var to = Object(target);
54 | for (var i = 1; i < arguments.length; i++) {
55 | var nextSource = arguments[i];
56 | if (nextSource === undefined || nextSource === null) continue;
57 | var keysArray = Object.keys(Object(nextSource));
58 | for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
59 | var nextKey = keysArray[nextIndex];
60 | var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
61 | if (desc !== undefined && desc.enumerable) to[nextKey] = nextSource[nextKey];
62 | }
63 | }
64 | return to;
65 | }
66 | }
67 |
68 | var ACTION_TYPES = {};
69 | /*
70 | * 可以多次调用,自动merge
71 | * */
72 | Fluxify.setActionTypes = function(types){
73 | ACTION_TYPES = Fluxify.utils.objectAssign(ACTION_TYPES, Fluxify.utils.keyMirror(types));
74 |
75 | return ACTION_TYPES;
76 | };
77 |
78 | Fluxify.getActionTypes = function(){
79 | return ACTION_TYPES;
80 | };
81 |
82 | /*
83 | * 移植自 Backbone.Event
84 | * todo: 改用eventemitter 或重写此部分
85 | * */
86 | Fluxify.Events = (function(){
87 | var hasOwnProperty = _OP.hasOwnProperty,
88 | toString = _OP.toString,
89 | slice = _AP.slice;
90 |
91 |
92 | var _ = {//methods from underscore that Events in use
93 | once: function (func) {
94 | var ran = false, memo;
95 | return function() {
96 | if (ran) return memo;
97 | ran = true;
98 | memo = func.apply(this, arguments);
99 | func = null;
100 | return memo;
101 | };
102 | },
103 | isObject: function (obj) {
104 | return obj === Object(obj);
105 | },
106 | isArray : Array.isArray || function(obj) {
107 | return toString.call(obj) == '[object Array]';
108 | },
109 | isString: function(obj){
110 | return toString.call(obj) == '[object String]';
111 | },
112 | has: function(obj, key) {
113 | return hasOwnProperty.call(obj, key);
114 | },
115 | keys : function (obj) {
116 | if (!_.isObject(obj)) return [];
117 | if (nativeKeys) return nativeKeys(obj);
118 | var keys = [];
119 | for (var key in obj) if (_.has(obj, key)) keys.push(key);
120 | return keys;
121 | },
122 | isEmpty : function (obj) {
123 | if (obj == null) return true;
124 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
125 | for (var key in obj) if (_.has(obj, key)) return false;
126 | return true;
127 | }
128 | }
129 |
130 | var Events = {
131 |
132 | // Bind an event to a `callback` function. Passing `"all"` will bind
133 | // the callback to all events fired.
134 | on: function(name, callback, context) {
135 | if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
136 | this._events || (this._events = {});
137 | var events = this._events[name] || (this._events[name] = []);
138 | events.push({callback: callback, context: context, ctx: context || this});
139 | return this;
140 | },
141 |
142 | // Bind an event to only be triggered a single time. After the first time
143 | // the callback is invoked, it will be removed.
144 | once: function(name, callback, context) {
145 | if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
146 | var self = this;
147 | var once = _.once(function() {
148 | self.off(name, once);
149 | callback.apply(this, arguments);
150 | });
151 | once._callback = callback;
152 | return this.on(name, once, context);
153 | },
154 |
155 | // Remove one or many callbacks. If `context` is null, removes all
156 | // callbacks with that function. If `callback` is null, removes all
157 | // callbacks for the event. If `name` is null, removes all bound
158 | // callbacks for all events.
159 | off: function(name, callback, context) {
160 | if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
161 |
162 | // Remove all callbacks for all events.
163 | if (!name && !callback && !context) {
164 | this._events = void 0;
165 | return this;
166 | }
167 |
168 | var names = name ? [name] : _.keys(this._events);
169 | for (var i = 0, length = names.length; i < length; i++) {
170 | name = names[i];
171 |
172 | // Bail out if there are no events stored.
173 | var events = this._events[name];
174 | if (!events) continue;
175 |
176 | // Remove all callbacks for this event.
177 | if (!callback && !context) {
178 | delete this._events[name];
179 | continue;
180 | }
181 |
182 | // Find any remaining events.
183 | var remaining = [];
184 | for (var j = 0, k = events.length; j < k; j++) {
185 | var event = events[j];
186 | if (
187 | callback && callback !== event.callback &&
188 | callback !== event.callback._callback ||
189 | context && context !== event.context
190 | ) {
191 | remaining.push(event);
192 | }
193 | }
194 |
195 | // Replace events if there are any remaining. Otherwise, clean up.
196 | if (remaining.length) {
197 | this._events[name] = remaining;
198 | } else {
199 | delete this._events[name];
200 | }
201 | }
202 |
203 | return this;
204 | },
205 |
206 | // Trigger one or many events, firing all bound callbacks. Callbacks are
207 | // passed the same arguments as `trigger` is, apart from the event name
208 | // (unless you're listening on `"all"`, which will cause your callback to
209 | // receive the true name of the event as the first argument).
210 | trigger: function(name) {
211 | if (!this._events) return this;
212 | var args = slice.call(arguments, 1);
213 | if (!eventsApi(this, 'trigger', name, args)) return this;
214 | var events = this._events[name];
215 | var allEvents = this._events.all;
216 | if (events) triggerEvents(events, args);
217 | if (allEvents) triggerEvents(allEvents, arguments);
218 | return this;
219 | },
220 |
221 | // Tell this object to stop listening to either specific events ... or
222 | // to every object it's currently listening to.
223 | stopListening: function(obj, name, callback) {
224 | var listeningTo = this._listeningTo;
225 | if (!listeningTo) return this;
226 | var remove = !name && !callback;
227 | if (!callback && typeof name === 'object') callback = this;
228 | if (obj) (listeningTo = {})[obj._listenId] = obj;
229 | for (var id in listeningTo) {
230 | obj = listeningTo[id];
231 | obj.off(name, callback, this);
232 | if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
233 | }
234 | return this;
235 | }
236 |
237 | };
238 |
239 | // Regular expression used to split event strings.
240 | var eventSplitter = /\s+/;
241 |
242 | // Implement fancy features of the Events API such as multiple event
243 | // names `"change blur"` and jQuery-style event maps `{change: action}`
244 | // in terms of the existing API.
245 | var eventsApi = function(obj, action, name, rest) {
246 | if (!name) return true;
247 |
248 | // Handle event maps.
249 | if (typeof name === 'object') {
250 | for (var key in name) {
251 | obj[action].apply(obj, [key, name[key]].concat(rest));
252 | }
253 | return false;
254 | }
255 |
256 | // Handle space separated event names.
257 | if (eventSplitter.test(name)) {
258 | var names = name.split(eventSplitter);
259 | for (var i = 0, length = names.length; i < length; i++) {
260 | obj[action].apply(obj, [names[i]].concat(rest));
261 | }
262 | return false;
263 | }
264 |
265 | return true;
266 | };
267 |
268 | // A difficult-to-believe, but optimized internal dispatch function for
269 | // triggering events. Tries to keep the usual cases speedy (most internal
270 | // Backbone events have 3 arguments).
271 | var triggerEvents = function(events, args) {
272 | var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
273 | switch (args.length) {
274 | case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
275 | case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
276 | case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
277 | case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
278 | default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
279 | }
280 | };
281 |
282 | // Aliases for backwards compatibility.
283 | Events.bind = Events.on;
284 | Events.unbind = Events.off;
285 |
286 | return Events;
287 | })();
288 |
289 | Fluxify.dispatcher = (function(){
290 |
291 | var tokenIndex = 0,
292 | tokenPrefix = "ID_";
293 |
294 | var Dispatcher = function(){
295 | this._isDispatching = false;
296 | this._currentPayload = undefined;
297 | this._callbacks = {};
298 | this._pending = {};
299 | this._handled = {};
300 | };
301 |
302 | Dispatcher.prototype = {
303 | constructor : Dispatcher,
304 | register: function(callback){
305 | var token = tokenPrefix + tokenIndex++;
306 |
307 | this._callbacks[token] = callback;
308 |
309 | return token;
310 | },
311 | unregister: function(token){
312 | delete this._callbacks[token];
313 | },
314 | dispatch : function(payload){
315 | if (this._isDispatching){
316 | console.error("cannot dispatch in middle of a dispatch");
317 | return;
318 | }
319 |
320 | this._startDispatching(payload);
321 |
322 | for (var token in this._callbacks){
323 | if (!hasOwn.call(this._callbacks, token)){
324 | continue;
325 | }
326 | this._invokeCallback(token);
327 |
328 | }
329 | this._afterDispatching();
330 | },
331 | _startDispatching: function(payload){
332 | this._currentPayload = payload;
333 | this._isDispatching = true;
334 | },
335 | _afterDispatching: function(){
336 | this._pending = {};
337 | this._handled = {};
338 | this._currentPayload = undefined;
339 | this._isDispatching = false;
340 | },
341 | _invokeCallback: function(token){
342 | var callback = this._callbacks[token];
343 |
344 | this._pending[token] = true;
345 | callback.call(this, this._currentPayload);
346 | this._handled[token] = true;
347 | },
348 | waitFor : function(tokens){
349 | for (var i = 0, len = tokens.length; i < len; i++){
350 | var token = tokens[i];
351 |
352 | if (typeof token === "object"){
353 | token = token.dispatchToken;
354 | }
355 |
356 | //check callback exist
357 | if (this._callbacks[token]){
358 | //已经开始执行
359 | if (this._pending[token]){
360 | //没有执行完,依赖一个执行中的callback说明成环
361 | if (!this._handled[token]){
362 | throw Error("circle dependencies found while waitFor :" + token);
363 | }
364 | }else{//没开始执行
365 | this._invokeCallback(token);
366 | }
367 | }else{
368 | console.error("callback not found for token: " + token);
369 | }
370 | }
371 | }
372 | }
373 |
374 | return new Dispatcher();
375 | })();
376 |
377 | var CHANGE_EVENT = "change";
378 | Fluxify.createStore = function(name, methods, onDispatching){
379 | var Store = Fluxify.utils.objectAssign({
380 | emitChange: function(data){
381 | this.trigger(CHANGE_EVENT, data);
382 | },
383 | onChange: function(listener, context){
384 | this.on(CHANGE_EVENT, listener, context);
385 | },
386 | offChange: function(listener){
387 | this.off(CHANGE_EVENT, listener);
388 | }
389 | }, Fluxify.Events, methods);
390 |
391 | if (typeof onDispatching === "function"){
392 | Store.dispatchToken = Fluxify.dispatcher.register(onDispatching.bind(Store));
393 | }
394 |
395 | return Store;
396 | }
397 |
398 | Fluxify.ReactMixins = (function(){
399 | var storeListenerHandler = function(method){
400 |
401 | var watchingStores = this.watchingStores,
402 | onStoreChange = this.onStoreChange || this.forceUpdate;
403 |
404 | if (!watchingStores || watchingStores.length < 0){
405 | console.log("no valid stores watching");
406 | }
407 |
408 | for (var i = 0, len = watchingStores.length, store = null; i < len; i++){
409 | store = watchingStores[i];
410 | if (store && typeof store[method] === "function"){
411 | store[method](onStoreChange, this);
412 | }
413 | }
414 | };
415 |
416 | return {
417 | componentDidMount : function(){
418 | storeListenerHandler.call(this, "onChange");
419 | },
420 | componentWillUnmount : function(){
421 | storeListenerHandler.call(this, "offChange");
422 | }
423 | }
424 | })();
425 |
426 | root.Fluxify = Fluxify;
427 | });
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Fluxify: 构建Flux应用的轻量化工具库
2 | ==================
3 |
4 | ##概述
5 | Fluxify是一个轻量化的(目前源码不足500行),协助构建[Flux][Flux]结构Web应用的工具库
6 |
7 | 由于facebook的[Flux][Flux]库更偏向于理念展示,公布的实现中只有Dispatcher部分。实际构建中有大量的重复工作可以由统一的工具库代劳,这也是Fluxify出现的原因。
8 |
9 | Fluxify目前做了以下事情:
10 |
11 | * ActionTypes的配置与访问
12 | * 内置单例的dispatcher实例,用户不必new Dispatcher
13 | * 提供createStore工厂方法用于生成Store,封装了事件和注册dispatcher回调的代码
14 | * 提供ReactMixins,简化React组件监听Store的代码
15 |
16 | ##使用
17 | ### 配置ActionTypes
18 | 提供了`Fluxify.setActionTypes`和`Fluxify.getActionTypes`两个工具函数,实际使用示例如下
19 | ```js
20 | // constant.js
21 | module.exports = {
22 | ACTION_TYPE: {
23 | THREAD_CLICK: null,
24 | CREATE_MESSAGE: null
25 | }
26 | }
27 |
28 | //appIndex.js
29 | var constant = require("constant");
30 |
31 | Fluxify.setActionTypes(constant.ACTION_TYPE);
32 |
33 | //then you can get ActionTypes anywhere by Fluxify.getActionTypes
34 | Fluxify.getActionTypes();// => Object {THREAD_CLICK: "THREAD_CLICK", CREATE_MESSAGE: "CREATE_MESSAGE"}
35 |
36 |
37 | ```
38 | 其中`Fluxify.setActionTypes`支持多次调用,最终保存的将是merge后的结果
39 |
40 | 为了简化配置,`Fluxify.setActionTypes`还提供了自动的KeyMirror功能
41 |
42 | 如下
43 | ```js
44 | Fluxify.setActionTypes({FOO: null});
45 | Fluxify.setActionTypes({BAR: 1});
46 |
47 | console.log(Fluxify.getActionTypes());
48 | // => Object {FOO: "FOO", BAR: 1}
49 | //KeyMirror只对值为null或undefined的key生效,避免覆盖用户配置
50 | ```
51 |
52 | ### 内置dispatcher
53 |
54 | 由于在Flux中dispatcher是单例的action转发器,因此没有由用户构建实例的必要,直接内置了一个dispatcher实例并通过`Fluxify.dispatcher`访问
55 |
56 | 保留了`register, unregister, waitFor, dispatch`等函数接口,其中waitFor不仅支持回调ID数组,也支持Store对象数组(由createStore构建的Store),如下:
57 |
58 | ```js
59 | var FooStore = Fluxify.createStore("FooStore", {/*methods*/}, function(payload){});
60 |
61 | //BarStore.js
62 | /*省略代码若干*/
63 |
64 | dispatcher.waitFor([FooStore.dispatchToken]);//等效于 dispatcher.waitFor([FooStore]);
65 |
66 | ```
67 |
68 | ###createStore工厂方法
69 |
70 | createStore对事件相关的代码进行了封装,同时规范了构造Store对象的过程。注意createStore构建出的Store是一个简单对象。
71 |
72 | createStore(name, methods, onDispatching)提供了三个参数:
73 |
74 | * `name` Store名,此参数目前还没有被用到,留作备用
75 | * `methods` Store的业务方法集合,与业务相关的方法应该放在这个对象中,最终将被以Object.assign的方式merge进返回的Store对象
76 | * `onDispatching(payload)` 注册在dispatcher上的回调函数,处理action相关的逻辑,此函数的this指向当前Store
77 |
78 | createStore返回的Store对象具备如下函数与属性:
79 |
80 | * `dispatchToken` dispatcher回调函数的回调ID
81 | * `emitChange()` 触发Store的change事件
82 | * `onChange(listener, context)` 添加change事件监听器
83 | * `offChange(listener)` 移除change事件监听器
84 |
85 | ###ReactMixins
86 |
87 | 使用示例
88 | ```js
89 | React.createClass({
90 | mixins: [Fluxify.ReactMixins],
91 | watchingStores: [FooStore, BarStore],
92 | /*
93 | 当watchingStores数组中的任一Store触发change事件时,onStoreChange函数即会被调用,this指向当前react组件。
94 | 可以省略onStoreChange函数,默认会调用this.forceUpdate
95 | */
96 | onStoreChange : function(){
97 | this.setState({
98 | foo: "foo"
99 | });
100 | },
101 | render: function(){/*...*/}
102 | });
103 |
104 | ```
105 | ###存在的问题与展望:
106 |
107 | 1. createStore保留了onDispatching函数,这意味着在onDispatching函数中一定会用`switch-case`语句来分发action逻辑,这种模式的缺点明显:`switch-case`本身的语法不够健壮,不同的action处理逻辑共享同一个作用域也存在风险(虽然可以用即时函数解决)。
之所以没有采用类似[Fluxxor][Fluxxor]的配置化转发Action,是存在几方面的考虑:
108 | * ActionTypes都是变量,无法通过对象字面量Mapping的方式书写(变量不能作为 字面量对象的key)。Fluxxor的解决方案是用bindActions函数参数奇偶位做区分,但个人认为这种方式在工程化的应用中显得不够严谨。
109 | * 采用声名式配置意味着每种Action都需要一个函数进行处理,这意味着Store会混杂业务函数与action处理函数。多数情况下每种Action的处理代码只有少数几行,而Store要监听的Action种类可能会很多,大量的action处理函数可能造成代码难以阅读和维护
110 | 2. 无论是官方库还是第三方实现,Flux架构目前缺少一块重要的拼图:**“异步调用前置化、Store逻辑同步化”是Flux的核心,但是目前为止没有足够健壮的手段来维护这种核心理念**。
有很多不理解Web API call前置理念的开发者,在声称自己实现了一个Flux框架时直接省略掉了ActionCreator,导致Store异步化并且迅速掉入复杂的store关系维护陷阱,无论用多么强大的组合事件机制都难以挽回。
Fluxify中依然存在着上面提到的**健壮**性缺失的问题,一句setTimeout都会让管理Store依赖的核心函数waitFor失效。或许像facebook这样工程师平均素质足够高的团队可以不用在乎,但从更普遍的工程角度这无疑是难以接受的。而这也将是Fluxify下一步思考的重点
111 |
112 | [Flux]: https://github.com/facebook/flux
113 |
114 | [Fluxxor]: http://fluxxor.com/
115 |
--------------------------------------------------------------------------------
/examples/chat/.gitignore:
--------------------------------------------------------------------------------
1 | /js/views/.module-cache
--------------------------------------------------------------------------------
/examples/chat/css/chatapp.css:
--------------------------------------------------------------------------------
1 | /**
2 | * This file is provided by Facebook for testing and evaluation purposes
3 | * only. Facebook reserves all rights not expressly granted.
4 | *
5 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
6 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
7 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
8 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
9 | * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
10 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
11 | */
12 |
13 | .chatapp {
14 | font-family: 'Muli', 'Helvetica Neue', helvetica, arial;
15 | max-width: 760px;
16 | margin: 20px auto;
17 | overflow: hidden;
18 | }
19 |
20 | .message-list, .thread-list {
21 | border: 1px solid #ccf;
22 | font-size: 16px;
23 | height: 400px;
24 | margin: 0;
25 | overflow-y: auto;
26 | padding: 0;
27 | }
28 |
29 | .message-section {
30 | float: right;
31 | width: 65%;
32 | }
33 |
34 | .thread-section {
35 | float: left;
36 | width: 32.5%;
37 | }
38 |
39 | .message-thread-heading,
40 | .thread-count {
41 | height: 40px;
42 | margin: 0;
43 | }
44 |
45 | .message-list-item, .thread-list-item {
46 | list-style: none;
47 | padding: 12px 14px 14px;
48 | }
49 |
50 | .thread-list-item {
51 | border-bottom: 1px solid #ccc;
52 | cursor: pointer;
53 | }
54 |
55 | .thread-list:hover .thread-list-item:hover {
56 | background-color: #f8f8ff;
57 | }
58 |
59 | .thread-list:hover .thread-list-item {
60 | background-color: #fff;
61 | }
62 |
63 | .thread-list-item.active,
64 | .thread-list:hover .thread-list-item.active,
65 | .thread-list:hover .thread-list-item.active:hover {
66 | background-color: #efefff;
67 | cursor: default;
68 | }
69 |
70 | .message-author-name,
71 | .thread-name {
72 | color: #66c;
73 | float: left;
74 | font-size: 13px;
75 | margin: 0;
76 | }
77 |
78 | .message-time, .thread-time {
79 | color: #aad;
80 | float: right;
81 | font-size: 12px;
82 | }
83 |
84 | .message-text, .thread-last-message {
85 | clear: both;
86 | font-size: 14px;
87 | padding-top: 10px;
88 | }
89 |
90 | .message-composer {
91 | box-sizing: border-box;
92 | font-family: inherit;
93 | font-size: 14px;
94 | height: 5em;
95 | width: 100%;
96 | margin: 20px 0 0;
97 | padding: 10px;
98 | }
99 |
100 | .message-sending {
101 | float: left;
102 | width: 1em;
103 | height: 1em;
104 | margin: 0 3px 0 0;
105 | background: url(../images/loading.png) -49px -33px no-repeat;
106 | }
107 |
108 |
--------------------------------------------------------------------------------
/examples/chat/images/loading.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kpaxqin/Fluxify/4ec8b1246123eb8a44ea1dbc01da7723c32117ca/examples/chat/images/loading.png
--------------------------------------------------------------------------------
/examples/chat/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | my chat
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
20 |
21 |
--------------------------------------------------------------------------------
/examples/chat/js/actions/ChatServerActionCreator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by cdyf on 15-2-4.
3 | */
4 | define(function(require){
5 | var Fluxify = require("../../lib/Fluxify");
6 |
7 | var dispatcher = Fluxify.dispatcher,
8 | ACTION_TYPE = Fluxify.getActionTypes();
9 |
10 | return {
11 | receiveAll: function(msgs){
12 | dispatcher.dispatch({
13 | action: {
14 | actionType: ACTION_TYPE.RECEIVE_RAW_MESSAGE,
15 | rawMessages: msgs
16 | }
17 | })
18 | },
19 | addMessage: function(message){
20 |
21 | dispatcher.dispatch({
22 | action: {
23 | actionType: ACTION_TYPE.ADD_RAW_MESSAGE,
24 | rawMessage: message
25 | }
26 | })
27 | }
28 | }
29 | });
--------------------------------------------------------------------------------
/examples/chat/js/actions/ChatViewActionCreator.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by cdyf on 15-2-5.
3 | */
4 | define(function(require){
5 | var Fluxify = require("../../lib/Fluxify");
6 |
7 | var MessageStore = require("../stores/MessageStore");
8 | var ThreadStore = require("../stores/ThreadStore");
9 |
10 | var WebAPIUtils = require("../utils/WebAPIUtils");
11 |
12 | var dispatcher = Fluxify.dispatcher,
13 | ACTION_TYPE = Fluxify.getActionTypes();
14 |
15 | return {
16 | threadClick: function(thread){
17 | dispatcher.dispatch({
18 | action: {
19 | actionType: ACTION_TYPE.THREAD_CLICK,
20 | thread: thread
21 | }
22 | });
23 |
24 | },
25 | createMessage: function(text){
26 | var message = MessageStore.getCreatedMessage(text);
27 |
28 | message.isSending = true;
29 |
30 | dispatcher.dispatch({
31 | action: {
32 | actionType: ACTION_TYPE.CREATE_MESSAGE,
33 | message: message
34 | }
35 | });
36 |
37 | WebAPIUtils.addMessage(message, ThreadStore.getCurrent());
38 | }
39 | }
40 | });
--------------------------------------------------------------------------------
/examples/chat/js/constants/Constants.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kpaxqin@github on 15-2-4.
3 | */
4 | define(function(){
5 | return {
6 | ACTION_TYPE: {
7 | RECEIVE_RAW_MESSAGE: null,
8 | ADD_RAW_MESSAGE : null,
9 | THREAD_CLICK: null,
10 | CREATE_MESSAGE: null
11 | },
12 | ACTION_SOURCE_TYPE: {
13 | SERVER: "SERVER",
14 | USER_INTERACTION: "USER_INTERACTION"
15 | }
16 |
17 | }
18 |
19 | })
--------------------------------------------------------------------------------
/examples/chat/js/index.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by cdyf on 15-2-4.
3 | */
4 | define(function(require){
5 | var Fluxify = require("../lib/Fluxify");
6 |
7 | var Constants = require("./constants/Constants");
8 |
9 | var IndexView = require("./views/index");
10 |
11 | var mockdata = require("./utils/mockdata");
12 |
13 | var WebAPIUtils = require("./utils/WebAPIUtils");
14 |
15 | mockdata.init();
16 |
17 | Fluxify.setActionTypes(Constants.ACTION_TYPE);
18 |
19 | return {
20 | init: function(){
21 | WebAPIUtils.getAllMessage();
22 |
23 | React.render(
24 | React.createElement(IndexView, null),
25 | document.getElementById("chat")
26 | )
27 | }
28 | }
29 |
30 | });
--------------------------------------------------------------------------------
/examples/chat/js/stores/MessageStore.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by cdyf on 15-2-4.
3 | */
4 | define(function(require){
5 |
6 | var Fluxify = require("../../lib/Fluxify");
7 |
8 | var dispatcher = Fluxify.dispatcher,
9 | ACTION_TYPE = Fluxify.getActionTypes();
10 |
11 | var ChatMessageUtil = require("../utils/ChatMessageUtil");
12 | var ThreadStore = require("./ThreadStore");
13 |
14 | var _messages = {};
15 |
16 | function _addMessages(raws){
17 | raws.forEach(function(msg){
18 | _messages[msg.id] = ChatMessageUtil.convertRawMessage(msg);
19 | }, this)
20 | }
21 |
22 | function _markReadByThread(threadID){
23 | for (var id in _messages){
24 | var msg = _messages[id];
25 | if (msg.threadID === threadID){
26 | msg.isRead = true;
27 | }
28 | }
29 | }
30 |
31 | var MessageStore = Fluxify.createStore("MessageStore", {
32 | getCreatedMessage: function(text){
33 | var timestamp = +new Date();
34 | return {
35 | id: "m_" + timestamp,
36 | threadID: ThreadStore.getCurrentID(),
37 | authorName: "Bill",
38 | date: new Date(timestamp),
39 | text: text,
40 | isRead: true
41 | }
42 | },
43 | getAll: function(){
44 | return _messages;
45 | },
46 | getAllForCurrentThread: function(){
47 | var currendThreadID = ThreadStore.getCurrentID();
48 | var result = [];
49 |
50 | for (var id in _messages){
51 | var msg = _messages[id];
52 |
53 | if (msg.threadID === currendThreadID){
54 | result.push(msg)
55 | }
56 | }
57 |
58 | return result;
59 | }
60 | }, function(payload){
61 | var action = payload.action;
62 |
63 | switch (action.actionType){
64 | case ACTION_TYPE.RECEIVE_RAW_MESSAGE :
65 | _addMessages(action.rawMessages);
66 | dispatcher.waitFor([ThreadStore.dispatchToken]);
67 | _markReadByThread(ThreadStore.getCurrentID());
68 | break;
69 | case ACTION_TYPE.THREAD_CLICK :
70 | dispatcher.waitFor([ThreadStore.dispatchToken]);
71 | _markReadByThread(ThreadStore.getCurrentID());
72 | this.emitChange();
73 | break;
74 | case ACTION_TYPE.CREATE_MESSAGE:
75 | _messages[action.message.id] = action.message;
76 | this.emitChange();
77 | break;
78 | case ACTION_TYPE.ADD_RAW_MESSAGE:
79 | delete _messages[action.rawMessage.id].isSending;
80 | this.emitChange();
81 | break;
82 | default :
83 | }
84 | });
85 |
86 | return MessageStore;
87 | })
--------------------------------------------------------------------------------
/examples/chat/js/stores/ThreadStore.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by cdyf on 15-2-4.
3 | */
4 | define(function(require){
5 | var Fluxify = require("../../lib/Fluxify");
6 |
7 | var dispatcher = Fluxify.dispatcher,
8 | ACTION_TYPE = Fluxify.getActionTypes();
9 |
10 | var ChatMessageUtil = require("../utils/ChatMessageUtil");
11 |
12 | var _threads = {},
13 | _currentID;
14 |
15 | var ThreadStore = Fluxify.createStore("ThreadStore", {
16 | init: function(rawMessages){
17 |
18 | //转换得到threads
19 | rawMessages.forEach(function(message){
20 | var threadID = message.threadID;
21 | var thread = _threads[threadID];
22 |
23 | //thread已保存且当前message时间不是最新
24 | if (thread && thread.lastTimestamp > message.timestamp){
25 | return;
26 | }
27 | _threads[threadID] = {
28 | id: threadID,
29 | name: message.threadName,
30 | lastMessage: ChatMessageUtil.convertRawMessage(message),
31 | lastTimestamp : message.timestamp
32 | }
33 | }, this);
34 |
35 | //计算current
36 | if (!_currentID){
37 | var sortedThreads = this.getAllChrono();
38 | this.setCurrentByID(sortedThreads[sortedThreads.length - 1].id);
39 | }
40 | },
41 | setCurrentByID: function(id){
42 | if (_threads[id]){
43 | _currentID = id;
44 | _threads[id].lastMessage.isRead = true;
45 | }
46 | },
47 | getByID: function(id){
48 | return _threads[id];
49 | },
50 | getAll: function(){
51 | return _threads;
52 | },
53 | getAllChrono: function(){
54 | var result = [];
55 |
56 | for (var id in _threads){
57 | var thread = _threads[id];
58 | result.push(thread)
59 | }
60 |
61 | result.sort(function(a, b){
62 | if (a.lastMessage.timestamp < b.lastMessage.timestamp) {
63 | return -1;
64 | } else if (a.lastMessage.timestamp > b.lastMessage.timestamp) {
65 | return 1;
66 | }
67 | return 0;
68 | })
69 |
70 | return result;
71 | },
72 | getCurrent: function(){
73 | return this.getByID(_currentID);
74 | },
75 | getCurrentID: function(){
76 | return _currentID;
77 | },
78 | /*
79 | * 由于标记message的isRead的工作在MessageStore中,此函数执行时计数出错,认为全部未读
80 | * */
81 | getUnreadCount: function(){
82 | var count = 0;
83 | for (var id in _threads){
84 | var thread = _threads[id];
85 |
86 | if (!thread.lastMessage.isRead){
87 | count++;
88 | }
89 | }
90 |
91 | return count;
92 | }
93 | }, function(payload){//通过createStore构造的dispatch监听函数this指向构造出的Store
94 | var action = payload.action;
95 |
96 | switch (action.actionType){
97 | case ACTION_TYPE.RECEIVE_RAW_MESSAGE :
98 | this.init(action.rawMessages);
99 |
100 | break;
101 | case ACTION_TYPE.THREAD_CLICK :
102 | this.setCurrentByID(action.thread.id);
103 | this.emitChange();
104 | break;
105 | case ACTION_TYPE.CREATE_MESSAGE:
106 | var currentThread = this.getCurrent();
107 |
108 | currentThread.lastMessage = action.message;
109 | currentThread.lastTimestamp = +action.message.date;
110 | this.emitChange();
111 |
112 | default :
113 | }
114 | });
115 |
116 | return ThreadStore;
117 |
118 | })
--------------------------------------------------------------------------------
/examples/chat/js/stores/UnreadThreadStore.js:
--------------------------------------------------------------------------------
1 | define(function(require){
2 | var Fluxify = require("../../lib/Fluxify");
3 |
4 | var dispatcher = Fluxify.dispatcher,
5 | ACTION_TYPE = Fluxify.getActionTypes();
6 |
7 | var ThreadStore = require("./ThreadStore");
8 | var MessageStore = require("./MessageStore");
9 |
10 | var UnreadThreadStore = Fluxify.createStore("UnreadThreadStore", {
11 | getUnreadThreadCount: function(){
12 | var count = 0,
13 | _threads = ThreadStore.getAll();
14 | for (var id in _threads){
15 | var thread = _threads[id];
16 |
17 | if (!thread.lastMessage.isRead){
18 | count++;
19 | }
20 | }
21 |
22 | return count;
23 | }
24 | }, function(payload){
25 | var action = payload.action;
26 |
27 | switch (action.actionType){
28 | case ACTION_TYPE.RECEIVE_RAW_MESSAGE:
29 | dispatcher.waitFor([ThreadStore.dispatchToken, MessageStore.dispatchToken])
30 | break;
31 | }
32 | });
33 |
34 | return UnreadThreadStore;
35 | })
--------------------------------------------------------------------------------
/examples/chat/js/utils/ChatMessageUtil.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by kpaxqin@github on 15-2-4.
3 | */
4 | define(function(require){
5 | return {
6 | convertRawMessage: function(rawMessage){
7 | return {
8 | id: rawMessage.id,
9 | threadID: rawMessage.threadID,
10 | authorName: rawMessage.authorName,
11 | date: new Date(rawMessage.timestamp),
12 | text: rawMessage.text,
13 | isRead: false//default to be false
14 | }
15 | }
16 | }
17 | });
--------------------------------------------------------------------------------
/examples/chat/js/utils/Events.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by cdyf on 15-2-4.
3 | */
4 | define(function(){
5 |
6 | var Events = (function(){
7 | var hasOwnProperty = Object.prototype.hasOwnProperty,
8 | toString = Object.prototype.toString,
9 | slice = Array.prototype.slice;
10 |
11 |
12 | var _ = {//methods from underscore that Events in use
13 | once: function (func) {
14 | var ran = false, memo;
15 | return function() {
16 | if (ran) return memo;
17 | ran = true;
18 | memo = func.apply(this, arguments);
19 | func = null;
20 | return memo;
21 | };
22 | },
23 | isObject: function (obj) {
24 | return obj === Object(obj);
25 | },
26 | isArray : Array.isArray || function(obj) {
27 | return toString.call(obj) == '[object Array]';
28 | },
29 | isString: function(obj){
30 | return toString.call(obj) == '[object String]';
31 | },
32 | has: function(obj, key) {
33 | return hasOwnProperty.call(obj, key);
34 | },
35 | keys : function (obj) {
36 | if (!_.isObject(obj)) return [];
37 | if (nativeKeys) return nativeKeys(obj);
38 | var keys = [];
39 | for (var key in obj) if (_.has(obj, key)) keys.push(key);
40 | return keys;
41 | },
42 | isEmpty : function (obj) {
43 | if (obj == null) return true;
44 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
45 | for (var key in obj) if (_.has(obj, key)) return false;
46 | return true;
47 | }
48 | }
49 |
50 | var Events = {
51 |
52 | // Bind an event to a `callback` function. Passing `"all"` will bind
53 | // the callback to all events fired.
54 | on: function(name, callback, context) {
55 | if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
56 | this._events || (this._events = {});
57 | var events = this._events[name] || (this._events[name] = []);
58 | events.push({callback: callback, context: context, ctx: context || this});
59 | return this;
60 | },
61 |
62 | // Bind an event to only be triggered a single time. After the first time
63 | // the callback is invoked, it will be removed.
64 | once: function(name, callback, context) {
65 | if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
66 | var self = this;
67 | var once = _.once(function() {
68 | self.off(name, once);
69 | callback.apply(this, arguments);
70 | });
71 | once._callback = callback;
72 | return this.on(name, once, context);
73 | },
74 |
75 | // Remove one or many callbacks. If `context` is null, removes all
76 | // callbacks with that function. If `callback` is null, removes all
77 | // callbacks for the event. If `name` is null, removes all bound
78 | // callbacks for all events.
79 | off: function(name, callback, context) {
80 | if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
81 |
82 | // Remove all callbacks for all events.
83 | if (!name && !callback && !context) {
84 | this._events = void 0;
85 | return this;
86 | }
87 |
88 | var names = name ? [name] : _.keys(this._events);
89 | for (var i = 0, length = names.length; i < length; i++) {
90 | name = names[i];
91 |
92 | // Bail out if there are no events stored.
93 | var events = this._events[name];
94 | if (!events) continue;
95 |
96 | // Remove all callbacks for this event.
97 | if (!callback && !context) {
98 | delete this._events[name];
99 | continue;
100 | }
101 |
102 | // Find any remaining events.
103 | var remaining = [];
104 | for (var j = 0, k = events.length; j < k; j++) {
105 | var event = events[j];
106 | if (
107 | callback && callback !== event.callback &&
108 | callback !== event.callback._callback ||
109 | context && context !== event.context
110 | ) {
111 | remaining.push(event);
112 | }
113 | }
114 |
115 | // Replace events if there are any remaining. Otherwise, clean up.
116 | if (remaining.length) {
117 | this._events[name] = remaining;
118 | } else {
119 | delete this._events[name];
120 | }
121 | }
122 |
123 | return this;
124 | },
125 |
126 | // Trigger one or many events, firing all bound callbacks. Callbacks are
127 | // passed the same arguments as `trigger` is, apart from the event name
128 | // (unless you're listening on `"all"`, which will cause your callback to
129 | // receive the true name of the event as the first argument).
130 | trigger: function(name) {
131 | if (!this._events) return this;
132 | var args = slice.call(arguments, 1);
133 | if (!eventsApi(this, 'trigger', name, args)) return this;
134 | var events = this._events[name];
135 | var allEvents = this._events.all;
136 | if (events) triggerEvents(events, args);
137 | if (allEvents) triggerEvents(allEvents, arguments);
138 | return this;
139 | },
140 |
141 | // Tell this object to stop listening to either specific events ... or
142 | // to every object it's currently listening to.
143 | stopListening: function(obj, name, callback) {
144 | var listeningTo = this._listeningTo;
145 | if (!listeningTo) return this;
146 | var remove = !name && !callback;
147 | if (!callback && typeof name === 'object') callback = this;
148 | if (obj) (listeningTo = {})[obj._listenId] = obj;
149 | for (var id in listeningTo) {
150 | obj = listeningTo[id];
151 | obj.off(name, callback, this);
152 | if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
153 | }
154 | return this;
155 | }
156 |
157 | };
158 |
159 | // Regular expression used to split event strings.
160 | var eventSplitter = /\s+/;
161 |
162 | // Implement fancy features of the Events API such as multiple event
163 | // names `"change blur"` and jQuery-style event maps `{change: action}`
164 | // in terms of the existing API.
165 | var eventsApi = function(obj, action, name, rest) {
166 | if (!name) return true;
167 |
168 | // Handle event maps.
169 | if (typeof name === 'object') {
170 | for (var key in name) {
171 | obj[action].apply(obj, [key, name[key]].concat(rest));
172 | }
173 | return false;
174 | }
175 |
176 | // Handle space separated event names.
177 | if (eventSplitter.test(name)) {
178 | var names = name.split(eventSplitter);
179 | for (var i = 0, length = names.length; i < length; i++) {
180 | obj[action].apply(obj, [names[i]].concat(rest));
181 | }
182 | return false;
183 | }
184 |
185 | return true;
186 | };
187 |
188 | // A difficult-to-believe, but optimized internal dispatch function for
189 | // triggering events. Tries to keep the usual cases speedy (most internal
190 | // Backbone events have 3 arguments).
191 | var triggerEvents = function(events, args) {
192 | var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
193 | switch (args.length) {
194 | case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
195 | case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
196 | case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
197 | case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
198 | default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
199 | }
200 | };
201 | /*
202 | todo: 暂不迁移IOC版本
203 | var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
204 |
205 | // Inversion-of-control versions of `on` and `once`. Tell *this* object to
206 | // listen to an event in another object ... keeping track of what it's
207 | // listening to.
208 | _.each(listenMethods, function(implementation, method) {
209 | Events[method] = function(obj, name, callback) {
210 | var listeningTo = this._listeningTo || (this._listeningTo = {});
211 | var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
212 | listeningTo[id] = obj;
213 | if (!callback && typeof name === 'object') callback = this;
214 | obj[implementation](name, callback, this);
215 | return this;
216 | };
217 | });
218 | */
219 |
220 | // Aliases for backwards compatibility.
221 | Events.bind = Events.on;
222 | Events.unbind = Events.off;
223 |
224 | return Events;
225 | })();
226 |
227 | return Events;
228 | })
--------------------------------------------------------------------------------
/examples/chat/js/utils/WebAPIUtils.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by cdyf on 15-2-4.
3 | */
4 | define(function(require){
5 | var ServerActionCreator = require("../actions/ChatServerActionCreator");
6 |
7 | return {
8 | getAllMessage: function(){
9 | var msgStr = localStorage.getItem("messages"),
10 | messages = JSON.parse(msgStr);
11 |
12 | ServerActionCreator.receiveAll(messages);
13 | },
14 | addMessage: function(message, thread){
15 | var rawMessages = JSON.parse(localStorage.getItem("messages")),
16 | newMessage = {
17 | id: message.id,
18 | threadID: message.threadID,
19 | threadName: thread.name,
20 | authorName: message.authorName,
21 | text: message.text,
22 | timestamp: +message.date
23 | };
24 | rawMessages.push(newMessage);
25 |
26 | //模拟服务器异步处理并返回结果
27 | setTimeout(function(){
28 | localStorage.setItem("messages", JSON.stringify(rawMessages));
29 | ServerActionCreator.addMessage(newMessage);
30 | }, 1000);
31 |
32 | }
33 | }
34 | })
--------------------------------------------------------------------------------
/examples/chat/js/utils/mockdata.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by cdyf on 15-2-4.
3 | */
4 | define(function(require){
5 | return {
6 | init: function() {
7 | // localStorage.clear();
8 | var existedMessages = localStorage.getItem("messages");
9 | if (existedMessages){
10 | return;
11 | }
12 | localStorage.clear();
13 | localStorage.setItem('messages', JSON.stringify([
14 | {
15 | id: 'm_1',
16 | threadID: 't_1',
17 | threadName: 'Jing and Bill',
18 | authorName: 'Bill',
19 | text: 'Hey Jing, want to give a Flux talk at ForwardJS?',
20 | timestamp: Date.now() - 99999
21 | },
22 | {
23 | id: 'm_2',
24 | threadID: 't_1',
25 | threadName: 'Jing and Bill',
26 | authorName: 'Bill',
27 | text: 'Seems like a pretty cool conference.',
28 | timestamp: Date.now() - 89999
29 | },
30 | {
31 | id: 'm_3',
32 | threadID: 't_1',
33 | threadName: 'Jing and Bill',
34 | authorName: 'Jing',
35 | text: 'Sounds good. Will they be serving dessert?',
36 | timestamp: Date.now() - 79999
37 | },
38 | {
39 | id: 'm_4',
40 | threadID: 't_2',
41 | threadName: 'Dave and Bill',
42 | authorName: 'Bill',
43 | text: 'Hey Dave, want to get a beer after the conference?',
44 | timestamp: Date.now() - 69999
45 | },
46 | {
47 | id: 'm_5',
48 | threadID: 't_2',
49 | threadName: 'Dave and Bill',
50 | authorName: 'Dave',
51 | text: 'Totally! Meet you at the hotel bar.',
52 | timestamp: Date.now() - 59999
53 | },
54 | {
55 | id: 'm_6',
56 | threadID: 't_3',
57 | threadName: 'Functional Heads',
58 | authorName: 'Bill',
59 | text: 'Hey Brian, are you going to be talking about functional stuff?',
60 | timestamp: Date.now() - 49999
61 | },
62 | {
63 | id: 'm_7',
64 | threadID: 't_3',
65 | threadName: 'Bill and Brian',
66 | authorName: 'Brian',
67 | text: 'At ForwardJS? Yeah, of course. See you there!',
68 | timestamp: Date.now() - 39999
69 | }
70 | ]));
71 | }
72 | }
73 | })
--------------------------------------------------------------------------------
/examples/chat/js/views/MessageComposer.js:
--------------------------------------------------------------------------------
1 | define(function(require){
2 |
3 | var ViewActionCreator = require("../actions/ChatViewActionCreator");
4 |
5 | var MessageStore = require("../stores/MessageStore");
6 |
7 | var ENTER_KEY_CODE = 13;
8 |
9 | var MessageComposer = React.createClass({displayName: 'MessageComposer',
10 | getInitialState: function(){
11 | return {
12 | text: ""
13 | }
14 | },
15 | _onKeyDown: function(e){
16 | if (e.keyCode === ENTER_KEY_CODE){
17 | e.preventDefault();
18 | var text = this.state.text.trim();
19 | if (text){
20 |
21 | // var message = MessageStore.getCreatedMessage(text);
22 |
23 | ViewActionCreator.createMessage(text);
24 |
25 | this.setState({text: ''});
26 | }
27 | }
28 | },
29 | _onChange: function(e){
30 | this.setState({
31 | text: e.target.value
32 | })
33 | },
34 | render: function(){
35 | return (
36 | React.createElement("textarea", {
37 | className: "message-composer",
38 | name: "message",
39 | onKeyDown: this._onKeyDown,
40 | onChange: this._onChange,
41 | value: this.state.text
42 | }
43 | )
44 | )
45 | }
46 | })
47 |
48 | return MessageComposer;
49 |
50 | });
--------------------------------------------------------------------------------
/examples/chat/js/views/MessageComposer.jsx:
--------------------------------------------------------------------------------
1 | define(function(require){
2 |
3 | var ViewActionCreator = require("../actions/ChatViewActionCreator");
4 |
5 | var MessageStore = require("../stores/MessageStore");
6 |
7 | var ENTER_KEY_CODE = 13;
8 |
9 | var MessageComposer = React.createClass({
10 | getInitialState: function(){
11 | return {
12 | text: ""
13 | }
14 | },
15 | _onKeyDown: function(e){
16 | if (e.keyCode === ENTER_KEY_CODE){
17 | e.preventDefault();
18 | var text = this.state.text.trim();
19 | if (text){
20 |
21 | // var message = MessageStore.getCreatedMessage(text);
22 |
23 | ViewActionCreator.createMessage(text);
24 |
25 | this.setState({text: ''});
26 | }
27 | }
28 | },
29 | _onChange: function(e){
30 | this.setState({
31 | text: e.target.value
32 | })
33 | },
34 | render: function(){
35 | return (
36 |
44 | )
45 | }
46 | })
47 |
48 | return MessageComposer;
49 |
50 | });
--------------------------------------------------------------------------------
/examples/chat/js/views/MessageListItem.js:
--------------------------------------------------------------------------------
1 | define(function(require){
2 |
3 |
4 | return React.createClass({
5 | render: function(){
6 | var msg = this.props.message;
7 |
8 | var Loading;
9 |
10 | if (msg.isSending){
11 | Loading = React.createElement("i", {className: "message-sending"})
12 | }
13 |
14 | return (
15 | React.createElement("li", {className: "message-list-item"},
16 | React.createElement("h5", {className: "message-author-name"}, msg.authorName),
17 | React.createElement("div", {className: "message-time"}, msg.date.toLocaleTimeString()),
18 | React.createElement("div", {className: "message-text"},
19 | Loading,
20 | msg.text
21 | )
22 | )
23 | )
24 | }
25 | })
26 | })
--------------------------------------------------------------------------------
/examples/chat/js/views/MessageListItem.jsx:
--------------------------------------------------------------------------------
1 | define(function(require){
2 |
3 |
4 | return React.createClass({
5 | render: function(){
6 | var msg = this.props.message;
7 |
8 | var Loading;
9 |
10 | if (msg.isSending){
11 | Loading =
12 | }
13 |
14 | return (
15 |
16 | {msg.authorName}
17 | {msg.date.toLocaleTimeString()}
18 |
19 | {Loading}
20 | {msg.text}
21 |
22 |
23 | )
24 | }
25 | })
26 | })
--------------------------------------------------------------------------------
/examples/chat/js/views/MessageSection.js:
--------------------------------------------------------------------------------
1 | define(function(require){
2 | var MessageStore = require("../stores/MessageStore");
3 | var ThreadStore = require("../stores/ThreadStore");
4 | var MessageListItem = require("./MessageListItem");
5 | var MessageComposer = require("./MessageComposer");
6 | var Fluxify = require("../../lib/Fluxify");
7 |
8 | return React.createClass({
9 |
10 | // _onChange: function(){
11 | // this.forceUpdate();
12 | // },
13 | // componentDidMount: function() {
14 | // MessageStore.onChange(this._onChange, this);
15 | // },
16 | //
17 | // componentWillUnmount: function() {
18 | // MessageStore.offChange(this._onChange);
19 | // },
20 | mixins: [Fluxify.ReactMixins],
21 | watchingStores: [MessageStore],
22 | render: function(){
23 | var thread = ThreadStore.getCurrent();
24 | var messages = MessageStore.getAllForCurrentThread();
25 |
26 | var msgListItems = messages.map(function(message){
27 | return (
28 | React.createElement(MessageListItem, {
29 | message: message}
30 | )
31 | )
32 |
33 | });
34 |
35 | return (
36 | React.createElement("div", {className: "message-section"},
37 | React.createElement("h3", {className: "message-thread-heading"},
38 | thread.name
39 | ),
40 | React.createElement("ul", {className: "message-list"},
41 | msgListItems
42 | ),
43 | React.createElement(MessageComposer, null)
44 | )
45 | )
46 | }
47 | })
48 |
49 | })
--------------------------------------------------------------------------------
/examples/chat/js/views/MessageSection.jsx:
--------------------------------------------------------------------------------
1 | define(function(require){
2 | var MessageStore = require("../stores/MessageStore");
3 | var ThreadStore = require("../stores/ThreadStore");
4 | var MessageListItem = require("./MessageListItem");
5 | var MessageComposer = require("./MessageComposer");
6 | var Fluxify = require("../../lib/Fluxify");
7 |
8 | return React.createClass({
9 |
10 | // _onChange: function(){
11 | // this.forceUpdate();
12 | // },
13 | // componentDidMount: function() {
14 | // MessageStore.onChange(this._onChange, this);
15 | // },
16 | //
17 | // componentWillUnmount: function() {
18 | // MessageStore.offChange(this._onChange);
19 | // },
20 | mixins: [Fluxify.ReactMixins],
21 | watchingStores: [MessageStore],
22 | render: function(){
23 | var thread = ThreadStore.getCurrent();
24 | var messages = MessageStore.getAllForCurrentThread();
25 |
26 | var msgListItems = messages.map(function(message){
27 | return (
28 |
31 | )
32 |
33 | });
34 |
35 | return (
36 |
37 |
38 | {thread.name}
39 |
40 |
43 |
44 |
45 | )
46 | }
47 | })
48 |
49 | })
--------------------------------------------------------------------------------
/examples/chat/js/views/ThreadListItem.js:
--------------------------------------------------------------------------------
1 | define(function(require){
2 | var ViewActionCreator = require("../actions/ChatViewActionCreator");
3 |
4 | return React.createClass({
5 |
6 | render: function(){
7 | var thread = this.props.thread,
8 | lastMsg = thread.lastMessage,
9 | isCurrent = this.props.isCurrent;
10 |
11 | var className = (function(){
12 | var base = "thread-list-item";
13 | return isCurrent ? base + " active": base;
14 | })()
15 |
16 | var onClick = function(){
17 | ViewActionCreator.threadClick(thread);
18 | }
19 |
20 | return (
21 | React.createElement("li", {className: className,
22 | onClick: onClick
23 | },
24 | React.createElement("h5", {className: "thread-name"}, thread.name),
25 | React.createElement("div", {className: "thread-time"},
26 | new Date(lastMsg.date.toLocaleTimeString())
27 | ),
28 | React.createElement("div", {className: "thread-last-message"},
29 | lastMsg.text
30 | )
31 | )
32 | )
33 | }
34 | })
35 |
36 | })
--------------------------------------------------------------------------------
/examples/chat/js/views/ThreadListItem.jsx:
--------------------------------------------------------------------------------
1 | define(function(require){
2 | var ViewActionCreator = require("../actions/ChatViewActionCreator");
3 |
4 | return React.createClass({
5 |
6 | render: function(){
7 | var thread = this.props.thread,
8 | lastMsg = thread.lastMessage,
9 | isCurrent = this.props.isCurrent;
10 |
11 | var className = (function(){
12 | var base = "thread-list-item";
13 | return isCurrent ? base + " active": base;
14 | })()
15 |
16 | var onClick = function(){
17 | ViewActionCreator.threadClick(thread);
18 | }
19 |
20 | return (
21 |
24 | {thread.name}
25 |
26 | {new Date(lastMsg.date.toLocaleTimeString())}
27 |
28 |
29 | {lastMsg.text}
30 |
31 |
32 | )
33 | }
34 | })
35 |
36 | })
--------------------------------------------------------------------------------
/examples/chat/js/views/ThreadSection.js:
--------------------------------------------------------------------------------
1 | define(function(require){
2 | var ThreadListItem = require("./ThreadListItem");
3 | var ThreadStore = require("../stores/ThreadStore");
4 | var UnreadThreadStore = require("../stores/UnreadThreadStore");
5 |
6 | var Fluxify = require("../../lib/Fluxify");
7 |
8 | var getInitState = function(){
9 | return {
10 | threads : ThreadStore.getAllChrono(),
11 | currentId: ThreadStore.getCurrentID(),
12 | // unreadCount: UnreadThreadStore.getUnreadThreadCount()
13 | unreadCount: ThreadStore.getUnreadCount()
14 | }
15 | }
16 |
17 |
18 | var ThreadSection = React.createClass({displayName: 'ThreadSection',
19 | getInitialState: function(){
20 | return getInitState();
21 | },
22 | mixins: [Fluxify.ReactMixins],
23 | watchingStores: [ThreadStore, UnreadThreadStore],
24 | onStoreChange: function(){
25 | this.setState(getInitState());
26 | },
27 | // _onChange: function(){
28 | // this.setState(getInitState());
29 | // },
30 | // componentDidMount: function() {
31 | // ThreadStore.onChange(this._onChange, this);
32 | // UnreadThreadStore.onChange(this._onChange, this);
33 | // },
34 | //
35 | // componentWillUnmount: function() {
36 | // ThreadStore.offChange(this._onChange);
37 | // UnreadThreadStore.offChange(this._onChange);
38 | // },
39 | render: function(){
40 | var self = this;
41 | var threadListItems = this.state.threads.map(function(thread){
42 | return (
43 | React.createElement(ThreadListItem, {
44 | key: thread.id,
45 | thread: thread,
46 | isCurrent: thread.id === self.state.currentId}
47 | )
48 | )
49 | });
50 |
51 | return (
52 | React.createElement("div", {className: "thread-section"},
53 | React.createElement("div", {className: "thread-count"},
54 | React.createElement("span", null, "Unread threads: "),
55 | React.createElement("span", null, this.state.unreadCount)
56 | ),
57 |
58 | React.createElement("ul", {className: "thread-list"},
59 | threadListItems
60 | )
61 | )
62 | )
63 |
64 | }
65 |
66 | });
67 |
68 | return ThreadSection;
69 | })
--------------------------------------------------------------------------------
/examples/chat/js/views/ThreadSection.jsx:
--------------------------------------------------------------------------------
1 | define(function(require){
2 | var ThreadListItem = require("./ThreadListItem");
3 | var ThreadStore = require("../stores/ThreadStore");
4 | var UnreadThreadStore = require("../stores/UnreadThreadStore");
5 |
6 | var Fluxify = require("../../lib/Fluxify");
7 |
8 | var getInitState = function(){
9 | return {
10 | threads : ThreadStore.getAllChrono(),
11 | currentId: ThreadStore.getCurrentID(),
12 | // unreadCount: UnreadThreadStore.getUnreadThreadCount()
13 | unreadCount: ThreadStore.getUnreadCount()
14 | }
15 | }
16 |
17 |
18 | var ThreadSection = React.createClass({
19 | getInitialState: function(){
20 | return getInitState();
21 | },
22 | mixins: [Fluxify.ReactMixins],
23 | watchingStores: [ThreadStore, UnreadThreadStore],
24 | onStoreChange: function(){
25 | this.setState(getInitState());
26 | },
27 | // _onChange: function(){
28 | // this.setState(getInitState());
29 | // },
30 | // componentDidMount: function() {
31 | // ThreadStore.onChange(this._onChange, this);
32 | // UnreadThreadStore.onChange(this._onChange, this);
33 | // },
34 | //
35 | // componentWillUnmount: function() {
36 | // ThreadStore.offChange(this._onChange);
37 | // UnreadThreadStore.offChange(this._onChange);
38 | // },
39 | render: function(){
40 | var self = this;
41 | var threadListItems = this.state.threads.map(function(thread){
42 | return (
43 |
48 | )
49 | });
50 |
51 | return (
52 |
53 |
54 | Unread threads:
55 | {this.state.unreadCount}
56 |
57 |
58 |
59 | {threadListItems}
60 |
61 |
62 | )
63 |
64 | }
65 |
66 | });
67 |
68 | return ThreadSection;
69 | })
--------------------------------------------------------------------------------
/examples/chat/js/views/index.js:
--------------------------------------------------------------------------------
1 | define(function(require){
2 | var ThreadSection = require("./ThreadSection");
3 |
4 | var MessageSection = require("./MessageSection");
5 |
6 | return React.createClass({
7 | render: function(){
8 | return (
9 | React.createElement("div", {className: "chatapp"},
10 | React.createElement(ThreadSection, null),
11 | React.createElement(MessageSection, null)
12 | )
13 | )
14 | }
15 | })
16 | });
--------------------------------------------------------------------------------
/examples/chat/js/views/index.jsx:
--------------------------------------------------------------------------------
1 | define(function(require){
2 | var ThreadSection = require("./ThreadSection");
3 |
4 | var MessageSection = require("./MessageSection");
5 |
6 | return React.createClass({
7 | render: function(){
8 | return (
9 |
10 |
11 |
12 |
13 | )
14 | }
15 | })
16 | });
--------------------------------------------------------------------------------
/examples/chat/lib/Fluxify.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by cdyf on 15-2-6.
3 | */
4 | (function(global, factory){
5 | // CMD & CMD-like
6 | if (typeof define ==="function"){
7 | define(function(require, exports, module){
8 | factory(global, exports);
9 | })
10 | }else {
11 | factory(global, {});
12 | }
13 | })(this, function(root, Fluxify){
14 |
15 | // var Fluxify = window.Fluxify = {};
16 |
17 | var _OP = Object.prototype,
18 | _AP = Array.prototype,
19 | hasOwn = _OP.hasOwnProperty;
20 |
21 |
22 | var ACTION_TYPES = {};
23 |
24 | Fluxify.utils = {
25 | /*
26 | * change {foo: null, bar: null} to {foo: "foo", bar: "bar"}
27 | * keyMirror({foo: 1}) ==> {"foo": 1}, we'll not overwrite existed value by default
28 | * keyMirror({foo: 1}, true) ==> {"foo" : "foo"}
29 | * */
30 | keyMirror: function(object, overwrite){
31 | overwrite = overwrite || false;
32 |
33 | var result = {};
34 |
35 | for (var key in object){
36 | if (!hasOwn.call(object, key)){
37 | continue;
38 | }
39 |
40 | if (object[key]){
41 | result[key] = overwrite ? key : object[key];
42 | }else{
43 | result[key] = key;
44 | }
45 | }
46 |
47 | return result;
48 | },
49 | /*
50 | * Object.assign polyfill from https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
51 | * */
52 | objectAssign: function(target, firstSource) {
53 | "use strict";
54 | if (target === undefined || target === null)
55 | throw new TypeError("Cannot convert first argument to object");
56 | var to = Object(target);
57 | for (var i = 1; i < arguments.length; i++) {
58 | var nextSource = arguments[i];
59 | if (nextSource === undefined || nextSource === null) continue;
60 | var keysArray = Object.keys(Object(nextSource));
61 | for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
62 | var nextKey = keysArray[nextIndex];
63 | var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
64 | if (desc !== undefined && desc.enumerable) to[nextKey] = nextSource[nextKey];
65 | }
66 | }
67 | return to;
68 | },
69 | extend : function(childProto){
70 | var child,
71 | parent = this,
72 | proxy = function(){};
73 |
74 | child = function(){
75 | return parent.apply(this, arguments);
76 | };
77 |
78 | proxy.prototype = parent.prototype;
79 |
80 | child.prototype = Fluxify.utils.objectAssign({
81 | constructor: child
82 | }, new proxy(), childProto);
83 |
84 | child.__super__ = parent;
85 |
86 | return child;
87 | }
88 | }
89 |
90 | /*
91 | * 可以多次调用,自动merge
92 | * */
93 | Fluxify.setActionTypes = function(types){
94 | ACTION_TYPES = Fluxify.utils.objectAssign(ACTION_TYPES, Fluxify.utils.keyMirror(types));
95 |
96 | return ACTION_TYPES;
97 | };
98 |
99 | Fluxify.getActionTypes = function(){
100 | return ACTION_TYPES;
101 | };
102 |
103 | /*
104 | * from Backbone.js
105 | * */
106 | Fluxify.Events = (function(){
107 | var hasOwnProperty = _OP.hasOwnProperty,
108 | toString = _OP.toString,
109 | slice = _AP.slice;
110 |
111 |
112 | var _ = {//methods from underscore that Events in use
113 | once: function (func) {
114 | var ran = false, memo;
115 | return function() {
116 | if (ran) return memo;
117 | ran = true;
118 | memo = func.apply(this, arguments);
119 | func = null;
120 | return memo;
121 | };
122 | },
123 | isObject: function (obj) {
124 | return obj === Object(obj);
125 | },
126 | isArray : Array.isArray || function(obj) {
127 | return toString.call(obj) == '[object Array]';
128 | },
129 | isString: function(obj){
130 | return toString.call(obj) == '[object String]';
131 | },
132 | has: function(obj, key) {
133 | return hasOwnProperty.call(obj, key);
134 | },
135 | keys : function (obj) {
136 | if (!_.isObject(obj)) return [];
137 | if (nativeKeys) return nativeKeys(obj);
138 | var keys = [];
139 | for (var key in obj) if (_.has(obj, key)) keys.push(key);
140 | return keys;
141 | },
142 | isEmpty : function (obj) {
143 | if (obj == null) return true;
144 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
145 | for (var key in obj) if (_.has(obj, key)) return false;
146 | return true;
147 | }
148 | }
149 |
150 | var Events = {
151 |
152 | // Bind an event to a `callback` function. Passing `"all"` will bind
153 | // the callback to all events fired.
154 | on: function(name, callback, context) {
155 | if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
156 | this._events || (this._events = {});
157 | var events = this._events[name] || (this._events[name] = []);
158 | events.push({callback: callback, context: context, ctx: context || this});
159 | return this;
160 | },
161 |
162 | // Bind an event to only be triggered a single time. After the first time
163 | // the callback is invoked, it will be removed.
164 | once: function(name, callback, context) {
165 | if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
166 | var self = this;
167 | var once = _.once(function() {
168 | self.off(name, once);
169 | callback.apply(this, arguments);
170 | });
171 | once._callback = callback;
172 | return this.on(name, once, context);
173 | },
174 |
175 | // Remove one or many callbacks. If `context` is null, removes all
176 | // callbacks with that function. If `callback` is null, removes all
177 | // callbacks for the event. If `name` is null, removes all bound
178 | // callbacks for all events.
179 | off: function(name, callback, context) {
180 | if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
181 |
182 | // Remove all callbacks for all events.
183 | if (!name && !callback && !context) {
184 | this._events = void 0;
185 | return this;
186 | }
187 |
188 | var names = name ? [name] : _.keys(this._events);
189 | for (var i = 0, length = names.length; i < length; i++) {
190 | name = names[i];
191 |
192 | // Bail out if there are no events stored.
193 | var events = this._events[name];
194 | if (!events) continue;
195 |
196 | // Remove all callbacks for this event.
197 | if (!callback && !context) {
198 | delete this._events[name];
199 | continue;
200 | }
201 |
202 | // Find any remaining events.
203 | var remaining = [];
204 | for (var j = 0, k = events.length; j < k; j++) {
205 | var event = events[j];
206 | if (
207 | callback && callback !== event.callback &&
208 | callback !== event.callback._callback ||
209 | context && context !== event.context
210 | ) {
211 | remaining.push(event);
212 | }
213 | }
214 |
215 | // Replace events if there are any remaining. Otherwise, clean up.
216 | if (remaining.length) {
217 | this._events[name] = remaining;
218 | } else {
219 | delete this._events[name];
220 | }
221 | }
222 |
223 | return this;
224 | },
225 |
226 | // Trigger one or many events, firing all bound callbacks. Callbacks are
227 | // passed the same arguments as `trigger` is, apart from the event name
228 | // (unless you're listening on `"all"`, which will cause your callback to
229 | // receive the true name of the event as the first argument).
230 | trigger: function(name) {
231 | if (!this._events) return this;
232 | var args = slice.call(arguments, 1);
233 | if (!eventsApi(this, 'trigger', name, args)) return this;
234 | var events = this._events[name];
235 | var allEvents = this._events.all;
236 | if (events) triggerEvents(events, args);
237 | if (allEvents) triggerEvents(allEvents, arguments);
238 | return this;
239 | },
240 |
241 | // Tell this object to stop listening to either specific events ... or
242 | // to every object it's currently listening to.
243 | stopListening: function(obj, name, callback) {
244 | var listeningTo = this._listeningTo;
245 | if (!listeningTo) return this;
246 | var remove = !name && !callback;
247 | if (!callback && typeof name === 'object') callback = this;
248 | if (obj) (listeningTo = {})[obj._listenId] = obj;
249 | for (var id in listeningTo) {
250 | obj = listeningTo[id];
251 | obj.off(name, callback, this);
252 | if (remove || _.isEmpty(obj._events)) delete this._listeningTo[id];
253 | }
254 | return this;
255 | }
256 |
257 | };
258 |
259 | // Regular expression used to split event strings.
260 | var eventSplitter = /\s+/;
261 |
262 | // Implement fancy features of the Events API such as multiple event
263 | // names `"change blur"` and jQuery-style event maps `{change: action}`
264 | // in terms of the existing API.
265 | var eventsApi = function(obj, action, name, rest) {
266 | if (!name) return true;
267 |
268 | // Handle event maps.
269 | if (typeof name === 'object') {
270 | for (var key in name) {
271 | obj[action].apply(obj, [key, name[key]].concat(rest));
272 | }
273 | return false;
274 | }
275 |
276 | // Handle space separated event names.
277 | if (eventSplitter.test(name)) {
278 | var names = name.split(eventSplitter);
279 | for (var i = 0, length = names.length; i < length; i++) {
280 | obj[action].apply(obj, [names[i]].concat(rest));
281 | }
282 | return false;
283 | }
284 |
285 | return true;
286 | };
287 |
288 | // A difficult-to-believe, but optimized internal dispatch function for
289 | // triggering events. Tries to keep the usual cases speedy (most internal
290 | // Backbone events have 3 arguments).
291 | var triggerEvents = function(events, args) {
292 | var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
293 | switch (args.length) {
294 | case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
295 | case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
296 | case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
297 | case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
298 | default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
299 | }
300 | };
301 | /*
302 | todo: 暂不迁移IOC版本
303 | var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
304 |
305 | // Inversion-of-control versions of `on` and `once`. Tell *this* object to
306 | // listen to an event in another object ... keeping track of what it's
307 | // listening to.
308 | _.each(listenMethods, function(implementation, method) {
309 | Events[method] = function(obj, name, callback) {
310 | var listeningTo = this._listeningTo || (this._listeningTo = {});
311 | var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
312 | listeningTo[id] = obj;
313 | if (!callback && typeof name === 'object') callback = this;
314 | obj[implementation](name, callback, this);
315 | return this;
316 | };
317 | });
318 | */
319 |
320 | // Aliases for backwards compatibility.
321 | Events.bind = Events.on;
322 | Events.unbind = Events.off;
323 |
324 | return Events;
325 | })();
326 |
327 | Fluxify.dispatcher = (function(){
328 |
329 | var tokenIndex = 0,
330 | tokenPrefix = "ID_";
331 |
332 | var Dispatcher = function(){
333 | this._isDispatching = false;
334 | this._currentPayload = undefined;
335 | this._callbacks = {};
336 | this._pending = {};
337 | this._handled = {};
338 | };
339 |
340 | Dispatcher.prototype = {
341 | constructor : Dispatcher,
342 | register: function(callback){
343 | var token = tokenPrefix + tokenIndex++;
344 |
345 | this._callbacks[token] = callback;
346 |
347 | return token;
348 | },
349 | unregister: function(token){
350 | delete this._callbacks[token];
351 | },
352 | dispatch : function(payload){
353 | if (this._isDispatching){
354 | console.error("cannot dispatch in middle of a dispatch");
355 | return;
356 | }
357 |
358 | this._startDispatching(payload);
359 |
360 | for (var token in this._callbacks){
361 | if (!hasOwn.call(this._callbacks, token)){
362 | continue;
363 | }
364 | this._invokeCallback(token);
365 |
366 | }
367 | this._afterDispatching();
368 | },
369 | _startDispatching: function(payload){
370 | this._currentPayload = payload;
371 | this._isDispatching = true;
372 | },
373 | _afterDispatching: function(){
374 | this._pending = {};
375 | this._handled = {};
376 | this._currentPayload = undefined;
377 | this._isDispatching = false;
378 | },
379 | _invokeCallback: function(token){
380 | var callback = this._callbacks[token];
381 |
382 | this._pending[token] = true;
383 | callback.call(this, this._currentPayload);
384 | this._handled[token] = true;
385 | },
386 | waitFor : function(tokens){
387 | for (var i = 0, len = tokens.length; i < len; i++){
388 | var token = tokens[i];
389 |
390 | if (typeof token === "object"){
391 | token = token.dispatchToken;
392 | }
393 |
394 | //check callback exist
395 | if (this._callbacks[token]){
396 | //已经开始执行
397 | if (this._pending[token]){
398 | //没有执行完,依赖一个执行中的callback说明成环
399 | if (!this._handled[token]){
400 | throw Error("circle dependencies found while waitFor :" + token);
401 | }
402 | }else{//没开始执行
403 | this._invokeCallback(token);
404 | }
405 | }else{
406 | console.error("callback not found for token: " + token);
407 | }
408 | }
409 | }
410 | }
411 |
412 | return new Dispatcher();
413 | })();
414 |
415 | var CHANGE_EVENT = "change";
416 | Fluxify.createStore = function(name, methods, onDispatching){
417 | var Store = Fluxify.utils.objectAssign({
418 | emitChange: function(data){
419 | this.trigger(CHANGE_EVENT, data);
420 | },
421 | onChange: function(listener, context){
422 | this.on(CHANGE_EVENT, listener, context);
423 | },
424 | offChange: function(listener){
425 | this.off(CHANGE_EVENT, listener);
426 | }
427 | }, Fluxify.Events, methods);
428 |
429 | if (typeof onDispatching === "function"){
430 | Store.dispatchToken = Fluxify.dispatcher.register(onDispatching.bind(Store));
431 | }
432 |
433 | return Store;
434 | }
435 |
436 | Fluxify.ReactMixins = (function(){
437 | var storeListenerHandler = function(method){
438 |
439 | var watchingStores = this.watchingStores,
440 | onStoreChange = this.onStoreChange || this.forceUpdate;
441 |
442 | if (!watchingStores || watchingStores.length < 0){
443 | console.log("no valid stores watching");
444 | }
445 |
446 | for (var i = 0, len = watchingStores.length, store = null; i < len; i++){
447 | store = watchingStores[i];
448 | if (store && typeof store[method] === "function"){
449 | store[method](onStoreChange, this);
450 | }
451 | }
452 | };
453 |
454 | return {
455 | componentDidMount : function(){
456 | storeListenerHandler.call(this, "onChange");
457 | },
458 | componentWillUnmount : function(){
459 | storeListenerHandler.call(this, "offChange");
460 | }
461 | }
462 | })();
463 |
464 | root.Fluxify = Fluxify;
465 | });
--------------------------------------------------------------------------------
/examples/chat/lib/sea.js:
--------------------------------------------------------------------------------
1 | /*! SeaJS 2.0.0b3 | seajs.org/LICENSE.md */
2 | (function(u,q){function H(a){return function(c){return Object.prototype.toString.call(c)==="[object "+a+"]"}}function P(a){a=a.replace(ia,"/");for(a=a.replace(ja,"$1/");a.match(Q);)a=a.replace(Q,"/");return a}function R(a){a=P(a);ka.test(a)?a=a.slice(0,-1):la.test(a)||(a+=".js");return a.replace(":80/","/")}function S(a,c){return ma.test(a)?a:na.test(a)?(c||v).match(I)[0]+a:oa.test(a)?(v.match(pa)||["/"])[0]+a.substring(1):j.base+a}function T(a,c){if(!a)return"";var b=a,d=j.alias,b=a=d&&d.hasOwnProperty(b)?
3 | d[b]:b,d=j.paths,f;if(d&&(f=b.match(qa))&&d&&d.hasOwnProperty(f[1]))b=d[f[1]]+f[2];f=b;var x=j.vars;x&&-1 ")),n.length=0,c(!0)):(aa[a]=f,W(f,c))}function d(a){!a&&e.status=ea)return a.exports;a.status=ea;b.resolve=c;b.async=function(a,d){D(c(a),d);return b};var d=a.factory,d=y(d)?d(b,a.exports={},a):d;a.exports=d===q?a.exports:d;a.status=za;return a.exports}function Y(a){for(var c=[],b=0;b1*navigator.userAgent.replace(/.*AppleWebKit\/(\d+)\..*/,"$1"),ya=/"(?:\\"|[^"])*"|'(?:\\'|[^'])*'|\/\*[\S\s]*?\*\/|\/(?:\\\/|[^/\r\n])+\/(?=[^\/])|\/\/.*|\.\s*require|(?:^|[^$])\brequire\s*\(\s*(["'])(.+?)\1\s*\)/g,
13 | xa=/\\\\/g,l=e.cache={},F,K={},L={},G={},aa={},ta=1,E=2,J=3,ea=4,za=5;B.prototype.destroy=function(){delete l[this.uri];delete L[this.uri]};var n=[];e.use=function(a,c){fa(function(){D(r(a),c)});return e};e.resolve=T;u.define=wa;B.load=D;var N=ga,ha=N.match(/^(.+?\/)(?:seajs\/)+(?:\d[^/]+\/)?$/);ha&&(N=ha[1]);var j=M.data={base:N,charset:"utf-8",preload:[]};e.config=M;var O,h=h.search.replace(/(seajs-\w+)(&|$)/g,"$1=1$2"),h=h+(" "+p.cookie);h.replace(/seajs-(\w+)=1/g,function(a,c){(O||(O=[])).push(c)});
14 | M({plugins:O});h=g.getAttribute("data-config");g=g.getAttribute("data-main");h&&j.preload.push(h);g&&e.use(g);if(m&&m.args){g=["define","config","use"];m=m.args;for(h=0;h