I'm trying to get an Elm app integrated with Masonry.js via ports but I'm having trouble trying to figure out how to get a Signal Html to trigger the port that tells Masonry.js to redraw the view.
I'm using StartApp and I'm not sure how to get a Signal that the view has finished re-rendering from a update call.
Alternate libraries that may work better with Elm or a fully Elm solutions would be appreciated too.
More detail about the overall problem that I'm trying to solve:
I have a series of images and I want to tile in in a masonry format (http://masonry.desandro.com/). They're represented by a list of objects in Elm which are converted to a list of divs in the view (with background-image set appropriately), but the images are of different sizes, hence the desire to tile them nicely. I'm using StartApp (http://package.elm-lang.org/packages/evancz/start-app/2.0.2/) to abstract the actual rendering of the html.
You can use ports in Elm to communicate with javascript in order to publish and subscribe to events in both directions. Let's build an example where a list of images is displayed with masonry layout, and clicking an image will remove it and trigger masonry to layout the remaining images.
Since the Elm app will send multiple types of events to javascript, we can create a single port that sends strings that javascript can then act upon. These strings will be commands that we can interpret in javascript in order to tell masonry to do certain things, like "initialize" and "imageRemoved". This port will also need a mailbox to which we can send messages from inside Elm.
masonryMailbox : Signal.Mailbox String
masonryMailbox =
Signal.mailbox ""
port masonryCommands : Signal String
port masonryCommands =
masonryMailbox.signal
Since you're using StartApp, you can return Effects in your update function, so let's create a function that will send messages to this mailbox from both the StartApp initializer and from inside the update function.
sendMasonryCommand : String -> Effects.Effects Action
sendMasonryCommand cmd =
let
task =
Signal.send masonryMailbox.address cmd
`Task.andThen` \_ -> Task.succeed NoOp
in
Effects.task task
You can send the "initialize" command during the StartApp init function like this:
init =
(initialModel, sendMasonryCommand "initialize")
In the update function, if we have a RemoveImage String action, we can send an "imageRemove" command to javascript:
update action model =
case action of
NoOp ->
(model, Effects.none)
RemoveImage url ->
let model' =
{ model
| images = List.filter ((/=) url) model.images
, message = "Removing image " ++ url
}
in (model', sendMasonryCommand "imageRemoved")
Now we need to wire up the javascript side of things to listen for these events. If javascript gets the command "initialize", then we can wire up masonry. If we get the "imageRemoved" command, we can just tell masonry to trigger the layout command again.
var app = Elm.fullscreen(Elm.Main);
app.ports.masonryCommands.subscribe(function(cmd) {
var $grid = $('.grid')
if (cmd === "initialize") {
$grid.masonry({
itemSelector: '.grid-item',
percentPosition: true,
columnWidth: '.grid-sizer'
});
$grid.imagesLoaded().progress( function() {
$grid.masonry();
});
} else if (cmd === "imageRemoved") {
$grid.masonry();
}
});
We can also wire up ports to send events back into Elm. Let's add onto the example by sending a message to Elm every time masonry completes its rendering. First we'll create a port called setMessage.
port setMessage : Signal String
Since this is a port that javascript will be publishing to, we only define the function signature in Elm, not the function itself. Instead, we have to give the signal an initial value from the javascript side when we call Elm.fullscreen(). The javascript changes to this:
var app = Elm.fullscreen(Elm.Main, { setMessage: "" });
Now, inside the javascript block that handles the "initialize" command, you can wire up masonry's layoutComplete function to send a message to this new port.
$grid.on("layoutComplete", function() {
app.ports.setMessage.send("Masonry layout complete!");
});
In order to consume these messages from the setMessage port in Elm, you'll need a SetMessage String action and you'll need to map the signal inside the StartApp inputs list. Your StartApp initializer code will look like this:
app =
StartApp.start
{ init = init
, view = view
, update = update
, inputs = [ Signal.map SetMessage setMessage ]
}
I've provided a complete working example of all of this in a few gists. Here is a gist containing the Elm code and here is a gist containing the html and javascript.
Related
I have a Meteor application where I use RiveScript, a chatbot module for Node. The module can save some aspects of user input. My issue is that when I run the module on server, the state is not saved for one user, but for all users. How would I go about creating a state for each user?
One method would be to create a new bot for each user like so:
let RiveScript = require('rivescript');
let users = {};
Meteor.onConnection(connection => {
users[connection.id] = new RiveScript({utf8: true});
users[connection.id].bot.loadDirectory('directory/',
() => {
users[connection.id].bot.sortReplies();
}
);
connection.onClose(() => {
delete users[connection.id]
})
});
However, in terms of memory management this can cause an issue. Are there any commonly used patterns in this regard?
I am not familiar with Meteor nor RiveScript, so I hope I'm answering your question.
If you want to keep a certain state for each conversation, e.g.
Chat #1 : the bot is waiting for the user to answer a particular question
Chat #2 : the bot is waiting for a command
...
Why don't you use a simple array mapping a conversation identifier (or in your case connection.id, I'm guessing) to its current state?
Example:
// Just to simulate an enumeration
var States = {WAITING: 0, WAITING_XXX_CONFIRMATION: 1, ...}
var state = [];
Meteor.onConnection(connection => {
state[connection.id] = States.WAITING;
});
// Somewhere else, e.g. in response to a command
if (state[connection.id] == WAITING_XXX_CONFIRMATION && input === 'yes') {
// do something
// reset the state
set[connection.id] = WAITING;
}
You could then keep track of the state of every conversation, and act accordingly. You could also wrap the state management inside an object to make it more reusable and nice to use (e.g. StateManager, to be able to make calls such as StateManager.of(chatId).set(newState).
Hope this helps.
Related to `chrome.webRequest.onBeforeRequest.removeListener`? -- How to stop a chrome web listener, I am trying to deregister a listener using dart:js
After invoking onBeforeRequest.callMethod('removeListener', [callback]); I notice that the listener is still being called. Furthermore, directly after adding a listener the hasListenerreturns false (even thought the listener is being registered).
var callback = (map) { /* some code */ };
var filter = new JsObject.jsify({"key": "value"});
var opt_extraInfoSpec = new JsObject.jsify(["extra opt"]);
// chrome.webRequest.onBeforeRequest.addListener
JsObject onBeforeRequest = context['chrome']['webRequest']['onBeforeRequest'];
onBeforeRequest.callMethod('addListener', [callback, filter, opt_extraInfoSpec]);
Logger.root.fine('main(): does callback exist: ${onBeforeRequest.callMethod('hasListener', [callback])}');
It seems to be necessary to follow 100% the dart:js recommendations how to use a dart Function in the javascript environment. I guess my problem was that the original dart dynamic function is wrapped automatically in a proxy. Hence the callMethod for addListener used a different proxy object then the callMethod for hasListener, even thought both of them were based on the same original dart object (i.e. callback).
The solution is to use the JsFunction and define the callback as following:
var callback = new JsFunction.withThis((that, map) { /* some code */ });
I have a number of event handlers in my page that were accessing global functions (functions defined in Script tags on the page). For instance:
<button id="ClearText" onclick="cleartb()">Clear Text Box</button>
That cleartb() function simply sits on the page:
<script>
function cleartb()
{
vm.essayText('');
return;
}
</script>
Now, vm is my page's view model (but for this question, all that matters is that it was simply a global variable available to the entire page) and I use functions and values it exposes in several event handlers, alert messages, etc.
The problem is that I've moved the definition of vm into a RequireJS AMD module called vm.js:
define(["knockout", "jquery"], function (ko, $) {
var essayText = 'Hello World!';
...
return {
essayText: essayText
}
});
When my onlick event handler runs or I refer to vm in any manner, I get a "vm undefined" error (as expected).
Question 1:
How can I give my page access to the vm variable defined in an AMD module especially if I don't want to "pollute" the global namespace? Is there a best-practice here?
Question 2:
Ultimately, I don't even want cleartb() on the page because it really is a view-model-specific operation. Although I think I can figure out what to do once I have the (an?) answer to Question 1, I would be interested to know how best to move the cleartb function into the vm AMD module so that I still can call it from my onlick event handler.
Note that I want values and function still to be exposed from a vm variable so that I can continue to use vm.cleartb() or inspect the value of vm.essayText() (it's a KO observable). (In other words, I don't want to solve the problem with a cleartb(vm) solution.)
Thank you for any help!
<script>
function cleartb()
{
vm.essayText('');
return;
}
alert(window.cleartb);
</script>
Actually, this way is already pollute the global window variable. So I think your first requirement don't make sense. And then you can do this way:
define(["knockout", "jquery"], function (ko, $) {
var essayText = 'Hello World!', varToBeExported;
...
window.varToBeExported = {
'cleartb': cleartb
};
return {
essayText: essayText
}
});
But if unnecessary, you should using requireJs way - require(['your moudle'],.... .
I am a newbie in using socket.io in Node JS - however I have coded a client (web page) / server program to render some statistics data at client side.
The requirement is like – to render a box statistics (out of many). Since the user can open multiple browser windows - we have a scenario where one box data can be requested by many times:
http://www.tool.com?boxname=box1
As such I want to achieve spawning one job for multiple requests for same box. Below is the logic I have implemented to meet the requirement:
Client Side:
Establishes a connection to server creating a data channel:
socket.on(boxname.toLowerCase(), function (data){
}
So whenever there is a data in boxname event I receive, parse the data and render it.
Server Side
First call an expect script to copy a script to run within an infinite loop on a box.
On successful copying of the script in the box - invoke another expect script to start its execution:
exports.ServiceMonitor = function(boxName, res) {
Step 1.
I need to copy a script as many times request comes else I would not be able to enter ‘exit’ event of this spawned process where I start the script execution as mentioned below:
var child = spawn('./scripts/copyscriptonbox.sh' , [boxName.toLowerCase(), getURL(boxName)]);
In the later part of the code I keep adding box names to boxnames variable which is declared global. So on a new boxname request - I search for number of occurrences of boxname in boxnames variable. If the count is 1 or more it would mean to me that a script is already running on that box:
var noofbox = boxnames.split(boxName.toLowerCase()).length - 1;
child.on('exit', function (code) {
logger.info('child stream exit: ' + code);
if(code == 0)
{
boxNames += boxName.toLowerCase();
logger.info('Box name added: ' + boxNames);
res.render('boxpage', {}); //render a web page
io.sockets.on('connection', function (socket) {
logger.info('Connected to box :' + boxName);
if(noofbox <= 0)
schild = spawn('./scripts/boxmon.sh', [boxName.toLowerCase(), getURL(boxName)]);
schild.on('exit', function (code) {
logger.info('schild stream exit.');
});
schild.stderr.on('data', function (data) {
logger.info('stderr: ' + data);
});
schild.stdout.on('data', function (data) {
//generate response
});
socket.on('disconnect',function(){logger.info('Disconnect Killed');ServiceMonitorEnd(boxName.toLowerCase());});
});
}
});
}
The issue is that if in two browser window I access URL : www.tool.com?boxname=box1 - first time I get the log only once (Connected to box : box1) but second time I get the same logs 2 times where as I was expecting it to be one time - I mean as many request comes in after the first one the logs gets printed that many times – if 5 then log gets printed for 1(first time)+2 (second time)+3(third time)+4 (fourth time)+5 (fifth time)? I understand that when ‘connection’ event is called for x times then it enters that many times for each connection.
How can I make the 'connection' event on the socket.io once for each request only?
Use socket.once instead of socket.on, it is another EventEmitter like .on, that is emitted only once, the first time. Check the docs. But remember after that any such events will not be received by the client.
I am creating an actionscript video player in Haxe and to avoid the asyncError I am trying to create a custom Object. How do I do this is Haxe?
The client property specifies the object on which callback methods are invoked. The default object is the NetStream object being created. If you set the client property to another object, callback methods will be invoked on that other object.
Here is my code.
public function new()
{
super();
trace("video");
//initialize net stream
nc = new NetConnection();
nc.connect(null);
ns = new NetStream(nc);
buffer_time = 2;
ns.bufferTime = buffer_time;
//Add video to stage
myVideo = new flash.media.Video(640, 360);
addChild(myVideo);
//Add callback method for listeing on NetStream meta data
client = new Dynamic();
ns.client = client;
client.onMetaData = metaDataHandler;
}
public function playVideo(url:String)
{
urlName = new String(url);
myVideo.attachNetStream(ns);
ns.play(urlName);
ns.addEventListener(NetStatusEvent.NET_STATUS, netstat);
}
function netstat(stats:NetStatusEvent)
{
trace(stats.info.code);
}
function metaDataHandler(infoObject:Dynamic)
{
myVideo.width = infoObject.width;
myVideo.height = infoObject.height;
}
You should probably do:
client : Dynamic = {};
Forget the client object; it isn't necessary for playing FLVs or for handling async errors. For that, just add a listener to the NetStream for AsyncErrorEvent.ASYNC_ERROR.
I suggest you add a listener to the NetConnection and the NetStream for NetStatusEvent.NET_STATUS, and then trace out the event.info.code value within the listener.
You should first see the string "NetConnection.Connect.Success" coming from the NetConnection; when you play your video through the NetStream, you should see "NetStream.Play.StreamNotFound" if there's a problem loading the FLV. Otherwise you should see "NetStream.Play.Start".
Unless you're progressively streaming your FLV, you may not see any video playing until the file has finished loading. If the movie file is long, this may explain why your program is running without errors but isn't playing the movie. There are small test FLV files available online that you might wish to use while you track the problem down.
(ActionScript's FLV playback API is bizarre and haXe's documentation is rudimentary, so you're rightfully frustrated.)
This maybe useful... http://code.google.com/p/zpartan/source/browse/zpartan/media/
You can see it being used
http://code.google.com/p/jigsawx/