diff --git a/docs/examples/feed.md b/docs/examples/feed.md
index 9c5a6bc..e2a238a 100644
--- a/docs/examples/feed.md
+++ b/docs/examples/feed.md
@@ -29,3 +29,23 @@ Subscribe
}
})
```
+
+Reload
+------
+```
+curl -u john:hello 127.0.0.1:10100/v1/application/30/feed/89/reload --digest
+{
+ "result": "Feed reloaded",
+ "status": "ok"
+}
+```
+
+Empty
+-----
+```
+curl -u john:hello 127.0.0.1:10100/v1/application/30/feed/86/empty --digest
+{
+ "result": "Feed empty done.",
+ "status": "ok"
+}
+```
diff --git a/docs/examples/javascript.md b/docs/examples/javascript.md
new file mode 100644
index 0000000..9055496
--- /dev/null
+++ b/docs/examples/javascript.md
@@ -0,0 +1,39 @@
+### JS client
+
+#### Usage
+```
+ window.onload = function() {
+ elasticfeed.init({
+ channel: {
+ url: 'ws://localhost:80',
+ transport: 'ws'
+ }
+ });
+
+ feed = elasticfeed.initFeed('000001', {
+ outputContainerId: 'my-elastic-feed-1',
+ stylerFunction: function(data) {
+ return '
' + data + '
';
+ }
+ });
+
+ feed.channel.on('join', function(chid, ts) {
+ feed1.addEntry(chid + " joined the chat room");
+ });
+
+ feed.channel.on('leave', function(chid, ts) {
+ feed1.addEntry(chid + " left the chat room");
+ });
+
+ window['socket'] = feed1.socket;
+ }
+```
+
+#### Test data broadcast
+```
+ // single feed
+ socket.send({Type:1, Timestamp:1111111, Content: {Type:3, Timestamp:22222, Id: "000001", Content: "data-examples"}})
+
+ // all feeds in the view on the chanel
+ socket.send({Type:1, Timestamp:1111111, Content: {Type:3, Timestamp:22222, Id: "*", Content: "data-examples"}})
+```
diff --git a/resource/entry.go b/resource/entry.go
index 9a77700..fdc2f48 100644
--- a/resource/entry.go
+++ b/resource/entry.go
@@ -8,60 +8,8 @@ import (
"github.com/feedlabs/feedify/graph"
"github.com/feedlabs/elasticfeed/service/stream/controller/room"
- "github.com/feedlabs/elasticfeed/service/stream/model"
)
-// user_feed_token = channel_id + feed_id => e.g aabbccddee + aabbcc
-// for private feeds there will be 1 websocket connection
-// for public company feeds will be 1 websocket connection
-// basically for each channel is 1 websocket connection
-// private and public channel will stream through multiple feed-pages events
-//
-// channel => channel_id
-// event => 'feed:' + feed_id
-// data => [{ // action object
-// id => string // entryId
-// tags => strings... // array of strings
-// action => string // add/delete/update
-// data => string // entry data as content; string e.g. json.stringify
-// }, {}, {}]
-
-const BODY_HEADER = `{
- "channel": "iO5wshd5fFE5YXxJ/hfyKQ==:17",
- "event": "CM_Action_Abstract:SEND:31",
- "data": {
- "action": {
- "actor": {
- "_type": 33,
- "_id": {
- "id": "1"
- },
- "id": 1,
- "displayName": "user1",
- "visible": true,
- "_class": "Feed_Model_User"
- },
- "verb": 13,
- "type": 31,
- "_class": "Feed_Action_Feed"
- },
- "model": {
- "_type": 33,
- "_id": {
- "id": "1"
- },
- "id": 1,
- "displayName": "user1",
- "visible": true,
- "_class": "Feed_Model_User"
- },
- "data": {`
-
-const BODY_BOTTOM = `
- }
- }
-}`
-
func GetEntryList(FeedId string, ApplicationId string, OrgId string) (feedEntries []*Entry, err error) {
feed, err := GetFeed(FeedId, ApplicationId, OrgId)
@@ -136,14 +84,11 @@ func AddEntry(feedEntry Entry, FeedId string, ApplicationId string, OrgId string
return "0", err
}
-// _data := BODY_HEADER + `"Id": "` + feedEntry.Id + `", "Action": "add", "Tag": {}, "Data": ` + strconv.Quote(feedEntry.Data) + BODY_BOTTOM
-// message.Publish(_data)
-
feedEntry.Id = strconv.Itoa(entry.Id)
// notify
- data, _ := json.Marshal(entry)
- room.Publish <- room.NewEntryEvent(model.EVENT_MESSAGE, "system", string(data))
+ d, _ := json.Marshal(feedEntry)
+ room.Publish <- room.NewFeedEvent(room.FEED_ENTRY_NEW, feed.Id, string(d))
return feedEntry.Id, nil
}
@@ -155,8 +100,12 @@ func UpdateEntry(id string, FeedId string, ApplicationId string, OrgId string, d
return err
}
- _data := BODY_HEADER + `"Id": "` + entry.Id + `", "Action": "update", "Tag": {}, "Data": ` + strconv.Quote(data) + BODY_BOTTOM
- message.Publish(_data)
+ // update entry
+ entry.Data = data
+
+ // notify
+ d, _ := json.Marshal(entry)
+ room.Publish <- room.NewEntryEvent(room.ENTRY_UPDATE, entry.Id, string(d))
_id, _ := strconv.Atoi(entry.Id)
return storage.SetPropertyNode(_id, "data", data)
@@ -176,8 +125,9 @@ func DeleteEntry(id string, FeedId string, ApplicationId string, OrgId string) (
storage.DeleteRelation(rel.Id)
}
- _data := BODY_HEADER + `"Id": "` + entry.Id + `", "Action": "remove"` + BODY_BOTTOM
- message.Publish(_data)
+ // notify
+ d, _ := json.Marshal(entry)
+ room.Publish <- room.NewEntryEvent(room.ENTRY_DELETE, entry.Id, string(d))
return storage.DeleteNode(_id)
}
diff --git a/resource/feed.go b/resource/feed.go
index c8a735a..632b00d 100644
--- a/resource/feed.go
+++ b/resource/feed.go
@@ -3,12 +3,10 @@ package resource
import (
"errors"
"strconv"
- "encoding/json"
"github.com/feedlabs/feedify/graph"
"github.com/feedlabs/elasticfeed/service/stream/controller/room"
- "github.com/feedlabs/elasticfeed/service/stream/model"
)
func (this *Feed) AddEntry(entry Entry) (EntryId string, err error) {
@@ -93,10 +91,6 @@ func AddFeed(feed Feed, applicationId string, orgId string) (id string, err erro
feed.Id = strconv.Itoa(_feed.Id)
- // notify
- data, _ := json.Marshal(feed)
- room.Publish <- room.NewFeedEvent(model.EVENT_MESSAGE, "system", string(data))
-
return feed.Id, nil
}
@@ -110,6 +104,14 @@ func DeleteFeed(id string) (error) {
return storage.DeleteNode(_id)
}
+func ActionReloadFeed(id string) {
+ room.Publish <- room.NewFeedEvent(room.FEED_RELOAD, id, "reload")
+}
+
+func ActionEmptyFeed(id string) {
+ room.Publish <- room.NewFeedEvent(room.FEED_EMPTY, id, "empty")
+}
+
func init() {
Feeds = make(map[string]*Feed)
}
diff --git a/service/db/v1/controller/feed.go b/service/db/v1/controller/feed.go
index e9a4b29..e421435 100644
--- a/service/db/v1/controller/feed.go
+++ b/service/db/v1/controller/feed.go
@@ -5,6 +5,8 @@ import (
"github.com/feedlabs/elasticfeed/resource"
"github.com/feedlabs/elasticfeed/service/db/v1/template/feed"
+
+ "github.com/feedlabs/elasticfeed/service/db/v1/template"
)
type FeedController struct {
@@ -152,3 +154,19 @@ func (this *FeedController) Delete() {
feed.ResponseDelete()
this.Controller.ServeJson()
}
+
+func (this *FeedController) ActionReload() {
+ feedId := this.Ctx.Input.Params[":feedId"]
+
+ resource.ActionReloadFeed(feedId)
+
+ this.ServeJson(template.Success("Feed reloaded."))
+}
+
+func (this *FeedController) ActionEmpty() {
+ feedId := this.Ctx.Input.Params[":feedId"]
+
+ resource.ActionEmptyFeed(feedId)
+
+ this.ServeJson(template.Success("Feed empty done."))
+}
diff --git a/service/db/v1/router/entry.go b/service/db/v1/router/entry.go
index 4adad9f..dfa9104 100644
--- a/service/db/v1/router/entry.go
+++ b/service/db/v1/router/entry.go
@@ -10,5 +10,5 @@ func init() {
feedify.Router("/v1/application/:applicationId:string/entry/:feedEntryId:int", &controller.EntryController{}, "get:Get;delete:Delete;put:Put")
feedify.Router("/v1/application/:applicationId:string/feed/:feedId:int/entry", &controller.EntryController{}, "get:GetListByFeed;post:PostToFeed")
- feedify.Router("/v1/application/:applicationId:string/feed/:feedId:int/entry/:feedEntryId:int", &controller.EntryController{}, "get:Get;delete:Remove")
+ feedify.Router("/v1/application/:applicationId:string/feed/:feedId:int/entry/:feedEntryId:int", &controller.EntryController{}, "get:Get;delete:Remove;put:Put")
}
diff --git a/service/db/v1/router/feed.go b/service/db/v1/router/feed.go
index bf70232..bde8170 100644
--- a/service/db/v1/router/feed.go
+++ b/service/db/v1/router/feed.go
@@ -8,4 +8,7 @@ import (
func init() {
feedify.Router("/v1/application/:applicationId:string/feed", &controller.FeedController{}, "get:GetList;post:Post")
feedify.Router("/v1/application/:applicationId:string/feed/:feedId:int", &controller.FeedController{}, "get:Get;delete:Delete;put:Put")
+
+ feedify.Router("/v1/application/:applicationId:string/feed/:feedId:int/reload", &controller.FeedController{}, "get:ActionReload")
+ feedify.Router("/v1/application/:applicationId:string/feed/:feedId:int/empty", &controller.FeedController{}, "get:ActionEmpty")
}
diff --git a/service/stream/controller/channel/long_pooling.go b/service/stream/controller/channel/long_pooling.go
index 67e5380..8e2d838 100644
--- a/service/stream/controller/channel/long_pooling.go
+++ b/service/stream/controller/channel/long_pooling.go
@@ -37,7 +37,7 @@ func (this *LongPollingController) Post() {
// should be executed and returned directly to user
// lastReceived time should not be changed in that case
- room.Publish <- room.NewSystemEvent(model.EVENT_MESSAGE, chid, data)
+ room.Publish <- room.NewSystemEvent(room.CHANNEL_MESSAGE, chid, data)
}
func (this *LongPollingController) Fetch() {
diff --git a/service/stream/controller/channel/websocket.go b/service/stream/controller/channel/websocket.go
index ea8619d..568cc00 100644
--- a/service/stream/controller/channel/websocket.go
+++ b/service/stream/controller/channel/websocket.go
@@ -6,7 +6,6 @@ import (
"github.com/feedlabs/feedify"
"github.com/gorilla/websocket"
- "github.com/feedlabs/elasticfeed/service/stream/model"
"github.com/feedlabs/elasticfeed/service/stream/controller/room"
)
@@ -43,7 +42,7 @@ func (this *WebSocketController) Join() {
return
}
- room.Publish <- room.NewSystemEvent(model.EVENT_MESSAGE, chid, string(p))
+ room.Publish <- room.NewSystemEvent(room.CHANNEL_MESSAGE, chid, string(p))
room.P2P <- ws
}
}
diff --git a/service/stream/controller/room/feed.go b/service/stream/controller/room/feed.go
index dc1fbdd..2ef6d2e 100644
--- a/service/stream/controller/room/feed.go
+++ b/service/stream/controller/room/feed.go
@@ -1,29 +1,41 @@
package room
import (
+ "strconv"
"time"
"container/list"
"encoding/json"
"github.com/feedlabs/feedify"
"github.com/gorilla/websocket"
-
"github.com/astaxie/beego/session"
-
"github.com/feedlabs/elasticfeed/service/stream/model"
)
const (
- FEED_ADD = iota
- FEED_DELETE
- FEED_UPDATE
- FEED_RESET
- FEED_RELOAD
-
- ENTRY_ADD
- ENTRY_DELETE
- ENTRY_UPDATE
- ENTRY_RELOAD
+ CHANNEL_JOIN = 0
+ CHANNEL_LEAVE = 1
+ CHANNEL_MESSAGE = 2
+
+ SYSTEM_FEED_MESSAGE = 1
+
+ FEED_RELOAD = 1
+ FEED_EMPTY = 2
+ FEED_ENTRY_NEW = 3
+ FEED_ENTRY_INIT = 4
+ FEED_ENTRY_MORE = 5
+ FEED_HIDE = 6
+ FEED_SHOW = 7
+ FEED_ENTRY_MESSAGE = 8
+ FEED_AUTHENTICATED = 100
+ FEED_AUTHENTICATION_REQUIRED = 101
+ FEED_AUTHENTICATION_FAILED = 102
+ FEED_LOGGED_OUT = 103
+
+ ENTRY_UPDATE = 1
+ ENTRY_DELETE = 2
+ ENTRY_SHOW = 3
+ ENTRY_HIDE = 4
)
var (
@@ -49,7 +61,8 @@ type Subscriber struct {
}
func NewEvent(ep model.EventType, user, msg string) model.Event {
- return model.Event{ep, user, time.Now().UnixNano(), msg}
+ ts := time.Now().UnixNano()
+ return model.Event{ep, user, ts, strconv.Itoa(int(ts)), msg}
}
func NewChannelEvent(ep model.EventType, user, msg string) model.Event {
@@ -57,15 +70,28 @@ func NewChannelEvent(ep model.EventType, user, msg string) model.Event {
}
func NewSystemEvent(ep model.EventType, user, msg string) model.Event {
- return NewChannelEvent(ep, user, msg)
+ event := NewEvent(ep, user, msg)
+ data, _ := json.Marshal(event)
+
+ return NewChannelEvent(CHANNEL_MESSAGE, user, string(data))
}
func NewFeedEvent(ep model.EventType, user, msg string) model.Event {
- return NewSystemEvent(ep, user, msg)
+ // "msg" is a feed action; can contain entry specific event
+ event := NewEvent(ep, user, msg)
+ data, _ := json.Marshal(event)
+
+ // "user" is and feed-id; "*" means all feeds on the client site
+ return NewSystemEvent(SYSTEM_FEED_MESSAGE, user, string(data))
}
func NewEntryEvent(ep model.EventType, user, msg string) model.Event {
- return NewSystemEvent(ep, user, msg)
+ // "msg" is a feed entry data as a string
+ event := NewEvent(ep, user, msg)
+ data, _ := json.Marshal(event)
+
+ // "*" all feeds on client site will receive this message
+ return NewFeedEvent(FEED_ENTRY_MESSAGE, "*", string(data))
}
func Join(user string, ws *websocket.Conn) {
@@ -82,10 +108,10 @@ func FeedManager() {
case sub := <-Subscribe:
Subscribers.PushBack(sub)
- Publish <- NewChannelEvent(model.EVENT_JOIN, sub.Name, "")
+ Publish <- NewChannelEvent(CHANNEL_JOIN, sub.Name, "")
case client := <-P2P:
- data, _ := json.Marshal(NewSystemEvent(model.EVENT_MESSAGE, "system", "ok"))
+ data, _ := json.Marshal(NewSystemEvent(CHANNEL_MESSAGE, "system", "ok"))
client.WriteMessage(websocket.TextMessage, data)
case event := <-Publish:
@@ -108,7 +134,7 @@ func FeedManager() {
ws.Close()
feedify.Error("WebSocket closed:", unsub)
}
- Publish <- NewChannelEvent(model.EVENT_LEAVE, unsub, "")
+ Publish <- NewChannelEvent(CHANNEL_LEAVE, unsub, "")
break
}
}
diff --git a/service/stream/model/event.go b/service/stream/model/event.go
index eb8049b..eb8b776 100644
--- a/service/stream/model/event.go
+++ b/service/stream/model/event.go
@@ -6,20 +6,15 @@ import (
type EventType int
-const (
- EVENT_JOIN = iota
- EVENT_LEAVE
- EVENT_MESSAGE
-)
-
type Event struct {
- Type EventType
- User string
- Timestamp int64
- Content string
+ Type EventType
+ User string
+ Ts int64
+ Timestamp string
+ Content string
}
-const archiveSize = 100
+const archiveSize = 1
var Archive = list.New()
@@ -34,7 +29,8 @@ func GetEvents(lastReceived int) []Event {
events := make([]Event, 0, Archive.Len())
for event := Archive.Front(); event != nil; event = event.Next() {
e := event.Value.(Event)
- if e.Timestamp > int64(lastReceived) {
+ if e.Ts > int64(lastReceived) {
+
events = append(events, e)
}
}
diff --git a/service/stream/static/elasticfeed.js b/service/stream/static/elasticfeed.js
index d592b96..4f155a8 100644
--- a/service/stream/static/elasticfeed.js
+++ b/service/stream/static/elasticfeed.js
@@ -10,49 +10,48 @@ function includeJs(jsFilePath) {
includeJs('lib/feed.js');
includeJs('lib/entry.js');
includeJs('lib/channel.js');
-includeJs('lib/event/channel.js');
-includeJs('lib/event/system.js');
+includeJs('lib/event.js');
(function(window) {
+ /** @type {Object} */
+ var defaultOptions = {
+ channel: {
+ url: 'localhost',
+ transport: 'ws'
+ },
+ stylerFunction: function(data) {
+ }
+ }
+
var elasticfeed = {
+ /** @type {Object} */
+ options: {},
+
/** @type {Object} */
channelList: {},
/** @type {Object} */
feedList: {},
- initFeed: function(options) {
- options = _extend({
- channel: {
- url: 'ws://localhost:80/ws',
- transport: 'ws'
- },
- styler: function(data) {
- }
- }, options);
-
- channel = this.getChannel(options.channel);
-
- return new Feed(options, channel);
+ init: function(options) {
+ this.options = _extend(defaultOptions, options);
},
- /**
- * Returns Feed object
- * @param options
- * @returns {*}
- */
- getFeed: function(options) {
- if (options.id == undefined) {
- return null;
+ initFeed: function(id, options) {
+ if (id == undefined) {
+ return false;
}
if (this.feedList[id] == undefined) {
- this.feedList[id] = new Feed(options)
+ opts = _extend(this.options, options || {});
+ channel = this.getChannel(opts.channel);
+
+ this.feedList[id] = new Feed(id, opts, channel);
}
- return this.feedList[options.id];
+ return this.feedList[id];
},
/**
@@ -63,7 +62,7 @@ includeJs('lib/event/system.js');
*/
getChannel: function(options, credential) {
if (options.url == undefined) {
- return null;
+ return false;
}
if (this.channelList[options.url] == undefined) {
@@ -74,11 +73,17 @@ includeJs('lib/event/system.js');
},
findFeed: function(id) {
- return this.getFeed({id: id});
+ if (this.feedList[id] == undefined) {
+ return false;
+ }
+ return this.feedList[id];
},
findChannel: function(url) {
- return this.getChannel({url: url});
+ if (this.channelList[url] == undefined) {
+ return false;
+ }
+ return this.channelList[url];
}
};
diff --git a/service/stream/static/lib/channel.js b/service/stream/static/lib/channel.js
index 8a6e711..24d1ba6 100644
--- a/service/stream/static/lib/channel.js
+++ b/service/stream/static/lib/channel.js
@@ -31,12 +31,15 @@ var Channel = (function() {
/** @type {Object} */
this._handlers = {};
+
+ /** @type {WebSocket} */
+ this._socket = null;
}
// Handlers
/**
- * @param {ChannelEvent} event
+ * @param {Event} event
* @param {Function} callback
*/
Channel.prototype.on = function(name, callback) {
@@ -64,18 +67,18 @@ var Channel = (function() {
// Events
/**
- * @param {ChannelEvent} event
+ * @param {Event} event
*/
Channel.prototype.onData = function(event) {
- switch (event.Type) {
+ switch (event.type) {
case JOIN:
- this.onJoin(event.User, event.ts)
+ this.onJoin(event.user, event.ts)
break;
case LEAVE:
- this.onLeave(event.User, event.ts)
+ this.onLeave(event.user, event.ts)
break;
case MESSAGE:
- this.onMessage(event.User, event.ts, event.Content)
+ this.onMessage(event.user, event.ts, event.content)
break;
}
}
@@ -93,13 +96,19 @@ var Channel = (function() {
}
Channel.prototype.onMessage = function(chid, timestamp, data) {
+ systemEvent = new Event(data);
+
for (var i in this._handlers[MESSAGE]) {
- this._handlers[MESSAGE][i].call(this, chid, timestamp, data);
+ this._handlers[MESSAGE][i].call(this, chid, timestamp, systemEvent);
}
}
// Connection
+ Channel.prototype.isWebSocket = function() {
+ return this._socket != undefined;
+ }
+
Channel.prototype.getConnection = function() {
}
@@ -108,7 +117,7 @@ var Channel = (function() {
self = this
this._socket.onmessage = function(event) {
- event = new ChannelEvent(JSON.parse(event.data))
+ event = new Event(JSON.parse(event.data))
self.onData(event)
};
@@ -124,7 +133,8 @@ var Channel = (function() {
var lastReceived = 0;
var isWait = false;
- this.getJSON('http://localhost:10100/stream/lp/join?chid=' + this.id, function() {
+ this.getJSON('http://localhost:10100/stream/lp/join?chid=' + this.id, function(data) {
+ // should set timestamp to proper one!
})
self = this;
@@ -144,7 +154,7 @@ var Channel = (function() {
}
self.each(data, function(i, event) {
- event = new ChannelEvent(event)
+ event = new Event(event)
self.onData(event)
lastReceived = event.GetTimestamp();
@@ -244,20 +254,6 @@ var Channel = (function() {
xhr1.send(dataString);
}
- Channel.prototype.EventToString = function(event) {
- switch (event.Type) {
- case JOIN:
- console.log(event.User + " joined the chat room");
- break;
- case LEAVE:
- console.log(event.User + " left the chat room");
- break;
- case MESSAGE:
- console.log(event.User + ", " + event.PrintContent());
- break;
- }
- }
-
// Helpers
var _extend = function(a, b) {
diff --git a/service/stream/static/lib/entry.js b/service/stream/static/lib/entry.js
index 149fefe..ced554a 100644
--- a/service/stream/static/lib/entry.js
+++ b/service/stream/static/lib/entry.js
@@ -1,80 +1,182 @@
var Entry = (function() {
- const ACTION_GROUP_TYPE = 2
-
- const ACTION_ADD = 1
- const ACTION_DELETE = 2
- const ACTION_UPDATE = 3
- const ACTION_HIDE = 4
- const ACTION_SHOW = 5
-
- const PUBLISH_METRIC = 6
+ const UPDATE = 1
+ const DELETE = 2
+ const HIDE = 3
+ const SHOW = 4
/** @type {Entry} */
var localCache = {}
- function Entry(data) {
+ function Entry(data, options) {
/** @type {String} */
this.id = null;
- /** @type {Integer} */
- this.ts = null;
+ /** @type {String} */
+ this.viewId = _uniqueId();
+
+ /** @type {String} */
+ this.data = data;
+
+ /** @type {Object} */
+ this._feed = null;
- /** @type {Feed} */
- this.parent = null;
+ /** @type {Function} */
+ this._styler = (options ? options.styler : undefined) || function() {
+ return data;
+ };
+
+ /** @type {Object} */
+ this._handlers = {};
}
- // Management
+ Entry.prototype.setParent = function(feed) {
+ this._feed = feed;
+ this.bindMessages();
+ }
- Entry.prototype.update = function() {
+ Entry.prototype.getViewId = function() {
+ return this.viewId;
}
- Entry.prototype.remove = function() {
+ // UI
+
+ // TODO:
+ // should make animations, should be configurable by developer
+ // first level is style function; second level is render function
+ Entry.prototype.render = function() {
+ try {
+ document.getElementById(this.viewId).innerHTML = this._styler.call(this, JSON.stringify(this.data));
+ } catch (e) {
+ // serious error
+ }
}
- Entry.prototype.hide = function() {
+ // Events
+
+ Entry.prototype.on = function(type, callback) {
+ switch (name) {
+ case 'delete':
+ type = DELETE
+ break;
+ case 'update':
+ type = UPDATE
+ break;
+ case 'hide':
+ type = HIDE
+ break;
+ case 'show':
+ type = SHOW
+ break;
+ default:
+ return false;
+ break;
+ }
+ if (this._handlers[type] == undefined) {
+ this._handlers[type] = []
+ }
+ this._handlers[type].push(callback);
+ return true;
}
- Entry.prototype.show = function() {
+ Entry.prototype.onData = function(entryEvent) {
+ switch (entryEvent.type) {
+ case DELETE:
+ this.onDelete(entryEvent.ts)
+ break;
+ case UPDATE:
+ this.onUpdate(entryEvent.ts, entryEvent.content)
+ break;
+ case HIDE:
+ this.onHide(entryEvent.ts)
+ break;
+ case SHOW:
+ this.onShow(entryEvent.ts)
+ break;
+ }
}
- // Events callbacks
+ // Management
- Entry.prototype.onBeforeUpdate = function(callback) {
+ Entry.prototype.update = function(timestamp, data) {
+ this.data = data;
+ this.render();
+ }
+ Entry.prototype.delete = function() {
+ this.unbindMessages();
+ document.getElementById(this.viewId).remove();
}
- Entry.prototype.onAfterUpdate = function(callback) {
+ Entry.prototype.hide = function() {
+ }
+ Entry.prototype.show = function() {
}
- Entry.prototype.onBeforeRemove = function(callback) {
+ // API
+
+ Entry.prototype.apiEntryUpdate = function(data) {
+ }
+ Entry.prototype.apiMetricSave = function(data) {
}
- Entry.prototype.onAfterRemove = function(callback) {
+ // Events callbacks
+
+ Entry.prototype.onUpdate = function(timestamp, data) {
+ this.update(timestamp, data);
+
+ for (var i in this._handlers[UPDATE]) {
+ this._handlers[UPDATE][i].call(this, timestamp, data);
+ }
+ }
+ Entry.prototype.onDelete = function(timestamp) {
+ for (var i in this._handlers[DELETE]) {
+ this._handlers[DELETE][i].call(this, timestamp);
+ }
}
- Entry.prototype.onData = function(callback) {
+ Entry.prototype.onHide = function(timestamp) {
+ for (var i in this._handlers[HIDE]) {
+ this._handlers[HIDE][i].call(this, timestamp);
+ }
+ }
+ Entry.prototype.onShow = function(timestamp) {
+ for (var i in this._handlers[SHOW]) {
+ this._handlers[SHOW][i].call(this, timestamp);
+ }
}
// Handlers
- Entry.prototype.registerHandlers = function() {
- // bind to feed events
+ Entry.prototype.bindMessages = function() {
+ var self = this;
+ this.__bindFeed = this._feed.on('entry-message', function(ts, entryEvent) {
+ if (entryEvent.id == self.id || entryEvent.id == '*') {
+ self.onData(entryEvent);
+ }
+ });
}
- // Helpers
-
- Entry.prototype.getParent = function() {
+ Entry.prototype.unbindMessages = function() {
+ this._feed.off(this.__bindFeed);
}
+ // Getters
+
Entry.prototype.getTimestamp = function() {
return this.ts;
}
+ // Helpers
+
+ var _uniqueId = function() {
+ return '_' + Math.random().toString(36).substr(2, 36);
+ }
+
return Entry;
})();
diff --git a/service/stream/static/lib/event.js b/service/stream/static/lib/event.js
new file mode 100644
index 0000000..5706608
--- /dev/null
+++ b/service/stream/static/lib/event.js
@@ -0,0 +1,48 @@
+var Event = (function() {
+
+ function Event(event) {
+
+ /** @type {String} */
+ this.id = event.Id || null;
+
+ /** @type {Integer} */
+ this.ts = event.Timestamp;
+
+ /** @type {Integer} */
+ this.actionGroup = null
+
+ /** @type {Integer} */
+ this.actionType = null
+
+ /** @type {String} */
+ this.user = event.User
+
+ /** @type {String} */
+ this.type = event.Type
+
+ /** @type {String} */
+ this.contentType = 'string'
+
+ /** @type {String} */
+ try {
+ this.content = JSON.parse(event.Content)
+ this.contentType = 'json'
+ } catch (e) {
+ this.content = event.Content
+ }
+ }
+
+ Event.prototype.GetTimestamp = function() {
+ return this.ts;
+ }
+
+ Event.prototype.PrintContent = function() {
+ if (this.contentType == 'string') {
+ return this.content
+ }
+ return JSON.stringify(this.content)
+ }
+
+ return Event;
+
+})();
diff --git a/service/stream/static/lib/event/channel.js b/service/stream/static/lib/event/channel.js
deleted file mode 100644
index dfbf253..0000000
--- a/service/stream/static/lib/event/channel.js
+++ /dev/null
@@ -1,48 +0,0 @@
-var ChannelEvent = (function() {
-
- function ChannelEvent(event) {
-
- /** @type {String} */
- this.id = null;
-
- /** @type {Integer} */
- this.ts = event.Timestamp;
-
- /** @type {Integer} */
- this.actionGroup = null
-
- /** @type {Integer} */
- this.actionType = null
-
- /** @type {String} */
- this.User = event.User
-
- /** @type {String} */
- this.Type = event.Type
-
- /** @type {String} */
- this.ContentType = 'string'
-
- /** @type {String} */
- try {
- this.Content = JSON.parse(event.Content)
- this.ContentType = 'json'
- } catch (e) {
- this.Content = event.Content
- }
- }
-
- ChannelEvent.prototype.GetTimestamp = function() {
- return this.ts;
- }
-
- ChannelEvent.prototype.PrintContent = function() {
- if (this.ContentType == 'string') {
- return this.Content
- }
- return JSON.stringify(this.Content)
- }
-
- return ChannelEvent;
-
-})();
diff --git a/service/stream/static/lib/event/entry.js b/service/stream/static/lib/event/entry.js
deleted file mode 100644
index fef5b97..0000000
--- a/service/stream/static/lib/event/entry.js
+++ /dev/null
@@ -1,48 +0,0 @@
-var EntryEvent = (function() {
-
- function EntryEvent(event) {
-
- /** @type {String} */
- this.id = null;
-
- /** @type {Integer} */
- this.ts = event.Timestamp;
-
- /** @type {Integer} */
- this.actionGroup = null
-
- /** @type {Integer} */
- this.actionType = null
-
- /** @type {String} */
- this.User = event.User
-
- /** @type {String} */
- this.Type = event.Type
-
- /** @type {String} */
- this.ContentType = 'string'
-
- /** @type {String} */
- try {
- this.Content = JSON.parse(event.Content)
- this.ContentType = 'json'
- } catch (e) {
- this.Content = event.Content
- }
- }
-
- EntryEvent.prototype.GetTimestamp = function() {
- return this.ts;
- }
-
- EntryEvent.prototype.PrintContent = function() {
- if (this.ContentType == 'string') {
- return this.Content
- }
- return JSON.stringify(this.Content)
- }
-
- return EntryEvent;
-
-})();
diff --git a/service/stream/static/lib/event/feed.js b/service/stream/static/lib/event/feed.js
deleted file mode 100644
index 3b0432e..0000000
--- a/service/stream/static/lib/event/feed.js
+++ /dev/null
@@ -1,48 +0,0 @@
-var FeedEvent = (function() {
-
- function FeedEvent(event) {
-
- /** @type {String} */
- this.id = null;
-
- /** @type {Integer} */
- this.ts = event.Timestamp;
-
- /** @type {Integer} */
- this.actionGroup = null
-
- /** @type {Integer} */
- this.actionType = null
-
- /** @type {String} */
- this.User = event.User
-
- /** @type {String} */
- this.Type = event.Type
-
- /** @type {String} */
- this.ContentType = 'string'
-
- /** @type {String} */
- try {
- this.Content = JSON.parse(event.Content)
- this.ContentType = 'json'
- } catch (e) {
- this.Content = event.Content
- }
- }
-
- FeedEvent.prototype.GetTimestamp = function() {
- return this.ts;
- }
-
- FeedEvent.prototype.PrintContent = function() {
- if (this.ContentType == 'string') {
- return this.Content
- }
- return JSON.stringify(this.Content)
- }
-
- return FeedEvent;
-
-})();
diff --git a/service/stream/static/lib/event/system.js b/service/stream/static/lib/event/system.js
deleted file mode 100644
index 2afedb6..0000000
--- a/service/stream/static/lib/event/system.js
+++ /dev/null
@@ -1,48 +0,0 @@
-var SystemEvent = (function() {
-
- function SystemEvent(event) {
-
- /** @type {String} */
- this.id = null;
-
- /** @type {Integer} */
- this.ts = event.Timestamp;
-
- /** @type {Integer} */
- this.actionGroup = null
-
- /** @type {Integer} */
- this.actionType = null
-
- /** @type {String} */
- this.User = event.User
-
- /** @type {String} */
- this.Type = event.Type
-
- /** @type {String} */
- this.ContentType = 'string'
-
- /** @type {String} */
- try {
- this.Content = JSON.parse(event.Content)
- this.ContentType = 'json'
- } catch (e) {
- this.Content = event.Content
- }
- }
-
- SystemEvent.prototype.GetTimestamp = function() {
- return this.ts;
- }
-
- SystemEvent.prototype.PrintContent = function() {
- if (this.ContentType == 'string') {
- return this.Content
- }
- return JSON.stringify(this.Content)
- }
-
- return SystemEvent;
-
-})();
diff --git a/service/stream/static/lib/feed.js b/service/stream/static/lib/feed.js
index 945dd1e..626fb6f 100644
--- a/service/stream/static/lib/feed.js
+++ b/service/stream/static/lib/feed.js
@@ -1,13 +1,15 @@
var Feed = (function() {
- const ACTION_GROUP_TYPE = 1
+ const SYSTEM_FEED_MESSAGE = 1
- const ACTION_RELOAD = 1
- const ACTION_RESET = 2
- const ACTION_DATA_INIT = 3
- const ACTION_DATA_MORE = 4
- const ACTION_HIDE = 5
- const ACTION_SHOW = 6
+ const RELOAD = 1
+ const EMPTY = 2
+ const ENTRY_NEW = 3
+ const ENTRY_INIT = 4
+ const ENTRY_MORE = 5
+ const HIDE = 6
+ const SHOW = 7
+ const ENTRY_MESSAGE = 8
const AUTHENTICATED = 100
const AUTHENTICATION_REQUIRED = 101
@@ -32,10 +34,10 @@ var Feed = (function() {
method: 'basic'
};
- function Feed(options, channel) {
+ function Feed(id, options, channel) {
/** @type {String} */
- this.id = null;
+ this.id = id;
/** @type {Channel} */
this.channel = channel;
@@ -45,74 +47,281 @@ var Feed = (function() {
/** @type {Object} */
if (this.channel.options.transport == 'ws') {
- this.socket = this.channel.getWebSocketConnection();
+ if (this.channel._socket == undefined) {
+ this.socket = this.channel.getWebSocketConnection();
+ } else {
+ this.socket = this.channel._socket;
+ }
} else if (this.channel.options.transport == 'lp') {
this.socket = this.channel.getLongPoolingConnection();
}
+ /** @type {Object} */
this.options = _extend(globalOptions, options);
- this._stylerFunction = options.styler || this._stylerFunction;
+
+ /** @type {Function} */
+ this.stylerFunction = options.stylerFunction || this._stylerFunction;
+
+ /** @type {Function} */
+ this.renderFunction = options.renderFunction || this._renderFunction;
+
+ /** @type {DOM} */
this.outputContainer = document.getElementById(this.options.outputContainerId);
this.bindChannel(this.channel);
+
+ /** @type {Object} */
+ this._handlers = {};
}
- Feed.prototype.on = function(type, callback) {
+ Feed.prototype.on = function(name, callback) {
+ switch (name) {
+ case 'reload':
+ type = RELOAD
+ break;
+ case 'empty':
+ type = EMPTY
+ break;
+ case 'entry':
+ type = ENTRY_NEW
+ break;
+ case 'entry-init':
+ type = ENTRY_INIT
+ break;
+ case 'entry-more':
+ type = ENTRY_MORE
+ break;
+ case 'hide':
+ type = HIDE
+ break;
+ case 'show':
+ type = SHOW
+ break;
+ case 'entry-message':
+ type = ENTRY_MESSAGE
+ break;
+ case 'authenticated':
+ type = AUTHENTICATED
+ break;
+ case 'authentication-required':
+ type = AUTHENTICATION_REQUIRED
+ break;
+ case 'authentication-failed':
+ type = AUTHENTICATION_FAILED
+ break;
+ case 'logout':
+ type = LOGGED_OUT
+ break;
+ default:
+ break;
+ }
+ if (this._handlers[type] == undefined) {
+ this._handlers[type] = []
+ }
+ this._handlers[type].push(callback);
+ return callback;
+ }
+
+ Feed.prototype.off = function(callback) {
+ for (var i in this._handlers) {
+ for (var x in this._handlers[i]) {
+ if (this._handlers[i][x] == callback) {
+ delete this._handlers[i][x];
+ return;
+ }
+ }
+ }
+ }
+
+ Feed.prototype.onData = function(feedEvent) {
+ switch (feedEvent.type) {
+ case RELOAD:
+ this.onReload(feedEvent.ts)
+ break;
+ case EMPTY:
+ this.onEmpty(feedEvent.ts)
+ break;
+ case ENTRY_NEW:
+ this.onEntryNew(feedEvent.ts, feedEvent.content)
+ break;
+ case ENTRY_INIT:
+ this.onEntryInit(feedEvent.ts, feedEvent.content)
+ break;
+ case ENTRY_MORE:
+ this.onEntryMore(feedEvent.ts, feedEvent.content)
+ break;
+ case HIDE:
+ this.onHide(feedEvent.ts)
+ break;
+ case SHOW:
+ this.onShow(feedEvent.ts)
+ break;
+ case ENTRY_MESSAGE:
+ this.onEntryMessage(feedEvent.ts, feedEvent.content)
+ break;
+ case AUTHENTICATED:
+ this.onAuthenticated(feedEvent.ts, feedEvent.content)
+ break;
+ case AUTHENTICATION_REQUIRED:
+ this.onAuthenticationRequired(feedEvent.ts, feedEvent.content)
+ break;
+ case AUTHENTICATION_FAILED:
+ this.onAuthenticationFailed(feedEvent.ts, feedEvent.content)
+ break;
+ case LOGGED_OUT:
+ this.onLogout(feedEvent.ts, feedEvent.content)
+ break;
+ }
}
// Events callbacks
- Feed.prototype.onReload = function(callback) {
+ Feed.prototype.onReload = function(timestamp) {
+ for (var i in this._handlers[RELOAD]) {
+ this._handlers[RELOAD][i].call(this, timestamp);
+ }
}
- Feed.prototype.onReset = function(callback) {
+ Feed.prototype.onEmpty = function(timestamp) {
+ for (var i in this._handlers[EMPTY]) {
+ this._handlers[EMPTY][i].call(this, timestamp);
+ }
}
- Feed.prototype.onEntryAdd = function(callback) {
+ Feed.prototype.onEntryNew = function(timestamp, data) {
+ entry = new Entry(data, {styler: this.stylerFunction});
+
+ this.addEntry(entry)
+
+ for (var i in this._handlers[ENTRY_NEW]) {
+ this._handlers[ENTRY_NEW][i].call(this, timestamp, entry);
+ }
}
- Feed.prototype.onEntryDelete = function(callback) {
+ Feed.prototype.onEntryInit = function(timestamp, data) {
+ entries = JSON.parse(data);
+
+ for (var i in this._handlers[ENTRY_INIT]) {
+ this._handlers[ENTRY_INIT][i].call(this, timestamp, entries);
+ }
}
- Feed.prototype.onEntryUpdate = function(callback) {
+ Feed.prototype.onEntryMore = function(timestamp, data) {
+ entries = JSON.parse(data);
+
+ for (var i in this._handlers[ENTRY_MORE]) {
+ this._handlers[ENTRY_MORE][i].call(this, timestamp, entries);
+ }
}
- Feed.prototype.onEvent = function(eventName, callback) {
+ Feed.prototype.onHide = function(timestamp) {
+ for (var i in this._handlers[HIDE]) {
+ this._handlers[HIDE][i].call(this, timestamp);
+ }
}
- Feed.prototype.onData = function(callback) {
+ Feed.prototype.onShow = function(timestamp) {
+ for (var i in this._handlers[SHOW]) {
+ this._handlers[SHOW][i].call(this, timestamp);
+ }
+ }
+
+ Feed.prototype.onEntryMessage = function(timestamp, content) {
+ entryEvent = new Event(content);
+
+ for (var i in this._handlers[ENTRY_MESSAGE]) {
+ this._handlers[ENTRY_MESSAGE][i].call(this, timestamp, entryEvent);
+ }
+ }
+
+ Feed.prototype.onAuthenticated = function(timestamp, content) {
+ for (var i in this._handlers[AUTHENTICATED]) {
+ this._handlers[AUTHENTICATED][i].call(this, timestamp);
+ }
+ }
+
+ Feed.prototype.onAuthenticationRequired = function(timestamp, content) {
+ for (var i in this._handlers[AUTHENTICATION_REQUIRED]) {
+ this._handlers[AUTHENTICATION_REQUIRED][i].call(this, timestamp);
+ }
+ }
+
+ Feed.prototype.onAuthenticationFailed = function(timestamp, content) {
+ for (var i in this._handlers[AUTHENTICATION_FAILED]) {
+ this._handlers[AUTHENTICATION_FAILED][i].call(this, timestamp);
+ }
+ }
+
+ Feed.prototype.onLogout = function(timestamp, content) {
+ for (var i in this._handlers[LOGGED_OUT]) {
+ this._handlers[LOGGED_OUT][i].call(this, timestamp);
+ }
}
// Entries management
- Feed.prototype.addEntry = function(data) {
- this.entryList.push(new Entry(data))
+ Feed.prototype.addEntry = function(entry) {
+
+ // types
+ // add by: timestamp up/down; always to top; always to bottom
+
+ entry.setParent(this);
+ this.entryList.push(entry);
+
+ this.outputContainer.innerHTML = '' + this.outputContainer.innerHTML;
+
+ entry.render();
}
- Feed.prototype.deleteEntry = function(id) {
+ Feed.prototype.deleteEntry = function(entry) {
+ entry.delete();
}
- Feed.prototype.updateEntry = function(id, data) {
+ Feed.prototype.updateEntry = function(entry, data) {
+ }
+
+ Feed.prototype.empty = function() {
+ for (var i in this.entryList) {
+ this.deleteEntry(this.entryList[i]);
+ delete this.entryList[i];
+ }
+ this.entryList = []
}
Feed.prototype.findEntry = function(id) {
}
+ // UI
+
+ Feed.prototype.render = function(id) {
+ for (var i in this.entryList) {
+ this.entryList[i].render();
+ }
+ }
+
// Handlers
Feed.prototype.bindChannel = function(channel) {
- channel.on('message', function(chid, ts, data) {
- // should detect type of message
- // if feed addressed then check id
- // trigger action if needed
+ var self = this;
+ channel.on('message', function(chid, ts, systemEvent) {
+ if (systemEvent.type == SYSTEM_FEED_MESSAGE) {
+ feedEvent = new Event(systemEvent.content);
+ if (feedEvent.user == self.id || feedEvent.user == '*') {
+ self.onData(feedEvent);
+ }
+ }
});
}
// Stylers
Feed.prototype._stylerFunction = function(data) {
- return JSON.stringify(data.Data);
+ return JSON.stringify(data);
+ }
+
+ Feed.prototype._renderFunction = function(data) {
+ return JSON.stringify(data);
}
// Helpers
diff --git a/service/stream/static/lp.html b/service/stream/static/lp.html
index 14a059e..85eca45 100644
--- a/service/stream/static/lp.html
+++ b/service/stream/static/lp.html
@@ -5,28 +5,28 @@
window.onload = function() {
- feed = elasticfeed.initFeed({
+ elasticfeed.init({
channel: {
url: 'http://localhost:80',
- transport: 'lp'
+ transport: 'ws'
}
});
- feed.channel.on('join', function(chid, ts) {
- console.log(chid + " joined the chat room");
- });
-
- feed.channel.on('leave', function(chid, ts) {
- console.log(chid + " left the chat room");
- });
-
- feed.channel.on('message', function(chid, ts, data) {
- console.log(chid + " " + JSON.stringify(data));
+ feed = elasticfeed.initFeed('1', {
+ channel: {
+ url: 'http://localhost:80',
+ transport: 'lp'
+ },
+ outputContainerId: 'my-elastic-feed',
+ stylerFunction: function(data) {
+ return '' + data + '
';
+ }
});
- window['socket'] = feed.socket
}
+
+