I'm trying to do pushState routing in PureScript, using the purescript-routing library. To help work it out, I've built the following minimal example:
module Main where
import Prelude
import Data.Foldable (oneOf)
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show (genericShow)
import Effect (Effect)
import Effect.Console (log)
import Flame (Html, QuerySelector(..))
import Flame.Application.NoEffects as FAN
import Flame.HTML.Attribute as HA
import Flame.HTML.Element as HE
import Routing.Match (Match, end, int, lit, root)
import Routing.PushState (makeInterface, matches)
import Signal.Channel (send)
type Model = {
route :: Route
}
data Message = ChangeRoute Route
data Route
= RouteOne
| RouteTwo
| RouteThree Int
| Root
derive instance genericRoute :: Generic Route _
instance showRoute :: Show Route where
show = genericShow
route :: Match Route
route = root *> oneOf
[ Root <$ end
, RouteOne <$ lit "route-1" <* end
, RouteTwo <$ lit "route-2" <* end
, RouteThree <$> (lit "route-3" *> int)
]
init :: Model
init = { route: Root }
update :: Model -> Message -> Model
update model = case _ of
ChangeRoute x -> model { route = x }
view :: Model -> Html Message
view model = HE.main "main" $
[ HE.p_ ("Route: " <> show model.route)
, HE.ul_
[ HE.li_
[ HE.a [ HA.href "/route-1" ] "route 1"
]
, HE.li_
[ HE.a [ HA.href "/route-2" ] "route 2"
]
, HE.li_
[ HE.a [ HA.href "/route-3/123" ] "route 3"
]
]
]
main :: Effect Unit
main = do
nav <- makeInterface
flameChannel <- FAN.mount (QuerySelector "main")
{ init
, update
, view
}
void $ nav # matches route \oldRoute newRoute -> do
log $ show oldRoute <> " -> " <> show newRoute
send flameChannel [ ChangeRoute newRoute ]
What works:
Route parsing
Printing the current route in the console
What doesn't work: Clicking a link in the DOM is handled by a page load, instead of a signal being sent to the application.
What code changes/additions need to be made so clicking a link results in a signal being sent to Flame, as opposed to a browser page load? Is my general approach even correct?
I've tried using the purescript-routing documentation and purescript-routing tests to gain an understanding, but neither show a complete example (including clickable URLs). I have also tried working from the RoutingPushHalogenClassic PureScript cookbook code, but it doesn't seem applicable to Flame.
This is one way to complete it:
module Main where
import Prelude
import Data.Foldable (oneOf)
import Data.Generic.Rep (class Generic)
import Data.Generic.Rep.Show (genericShow)
import Data.Maybe (Maybe(..), isNothing)
import Debug.Trace (spy)
import Effect (Effect)
import Effect.Console (log)
import Flame (Html, QuerySelector(..))
import Flame.Application.NoEffects as FAN
import Flame.HTML.Attribute as HA
import Flame.HTML.Element as HE
import Foreign (unsafeToForeign)
import Routing.Match (Match, end, int, lit, root)
import Routing.PushState (PushStateInterface, makeInterface, matches)
import Signal.Channel (Channel, send)
import Web.Event.Event (preventDefault)
type Model
= { navInterface :: PushStateInterface
, route :: Route
}
data Message
= ChangeRouteInternal Route
| ChangeRouteExternal Route
data Route
= RouteOne
| RouteTwo
| RouteThree Int
| Root
derive instance genericRoute :: Generic Route _
instance showRoute :: Show Route where
show = genericShow
route :: Match Route
route =
root
*> oneOf
[ Root <$ end
, RouteOne <$ lit "route-1" <* end
, RouteTwo <$ lit "route-2" <* end
, RouteThree <$> (lit "route-3" *> int) <* end
]
init :: PushStateInterface -> Model
init nav = { navInterface: nav, route: Root }
update :: Model -> Message -> Model
update model = case _ of
ChangeRouteInternal x -> spy "ChangeRouteInternal" model { route = x }
ChangeRouteExternal x -> spy "ChangeRouteExternal" model { route = x }
view :: Model -> Html Message
view model =
HE.main "main"
$ [ HE.p_ ("Route: " <> show model.route)
, HE.ul_
[ HE.li_
[ HE.a (routeAnchorAttributes (ChangeRouteInternal RouteOne)) "route 1"
]
, HE.li_
[ HE.a (routeAnchorAttributes (ChangeRouteInternal RouteTwo)) "route 2"
]
, HE.li_
[ HE.a (routeAnchorAttributes (ChangeRouteInternal (RouteThree 123))) "route 3"
]
]
]
where
routeAnchorAttributes = case _ of
ChangeRouteInternal anchorRoute -> [ HA.href (routeToUrl anchorRoute), onClick_ anchorRoute ]
_ -> []
-- Based on keypress example at:
-- https://github.com/easafe/purescript-flame/blob/master/test/Basic/EffectList.purs
onClick_ clickedRoute =
HA.createRawEvent "click"
$ \event -> do
preventDefault event
model.navInterface.pushState (unsafeToForeign {}) (routeToUrl clickedRoute)
pure $ Just (ChangeRouteInternal clickedRoute)
routeToUrl :: Route -> String
routeToUrl = case _ of
Root -> "/"
RouteOne -> "/route-1"
RouteTwo -> "/route-2"
RouteThree n -> "/route-3/" <> (show n)
routeMatch :: Match Route -> Channel (Array Message) -> PushStateInterface -> Effect Unit
routeMatch m chan =
void
<$> matches m \oldRoute newRoute -> do
log $ show oldRoute <> " -> " <> show newRoute
if isNothing oldRoute then
send chan [ ChangeRouteExternal newRoute ]
else
pure unit
main :: Effect Unit
main = do
nav <- makeInterface
flameChannel <-
FAN.mount (QuerySelector "main")
{ init: init nav
, update
, view
}
routeMatch route flameChannel nav
This code has separate Message concrete types for route changes originating from inside the application versus outside, i.e. from the user typing in the address bar, loading the application from an external link, etc. This is not necessary, but I wanted to highlight that the logic and code paths for these circumstances are different.
routeMatch handles external routing changes, onClick_ is for internal and uses Flame's own createRawEvent function.
While the Flame docs don't cover click handling and preventDefault, I did find this test of key capture very helpful when making onClick_.
Open the developer tools console to see messages and internal state changes.
Related
I'm currently having an issue that has been driving me crazy for the past 30-40 mins. I've successfully created a products api using Django/Django REST framework. But when I call the endpoint to try and consume it with angular, I get a 404 error. When I open the console I am greeted with an error that states GET http://localhost:8000/static/api/products/ 404 (Not Found). However, when I navigate to the URL manually in browser I get the browsable api menu.
I'm not exactly sure what's going on here...but I'm thinking it could be because of my angular static URLs/roots.
Here is the current code for the main area URLs of the app:
from django.conf.urls import url,include
from django.contrib import admin
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
url(r'^admin/', admin.site.urls),
url(r'^api/', include('customerreview_rest.urls', namespace='api')),
url(r'^api/',include ('products_rest.urls', namespace='api')),
url(r'^', include('GemStore_App.urls',namespace='frontend')),
] + static(settings.MEDIA_URL,document_root = settings.MEDIA_ROOT)
Here is the products URL/root for the endpoint:
from django.conf.urls import url,include
from rest_framework import routers
from products_rest.viewsets import ProductsViewsets
router = routers.DefaultRouter()
router.register('products',ProductsViewsets,'products')
urlpatterns = [
url(r'^', include(router.urls)) #base router
]
Lastly, here is the code for the static angular files:
from django.conf.urls import url, include
from django.views.generic.base import RedirectView
urlpatterns = [
url(r'^$', RedirectView.as_view(url='static/index.html', permanent=False), name='index')
]
Here also, is the code I planed to use to consume the api using Angular:
myStore.controller("myStoreController",function($scope,$http){
$scope.gems = $http.get("api/products/").then(
function(response){
$scope.gems = response.data
}, function(error){
$scope.error = error
});
})
Any light that can be shed on this topic, or maybe a point in the right direction would be greatly appreciated.
Thanks
Turns out that the issue was that I needed to include a another / in my call in order for it to be accepted.
So in summary, the code for the angular portian looks like this now:
myStore.controller("myStoreController",function($scope,$http){
$scope.gems = $http.get("/api/products/").then(
function(response){
$scope.gems = response.data
}, function(error){
$scope.error = error
});
})
#Sayse Thanks for helping.
I am trying to put in place a unit test for websocket. From the doc, I should be able to use WS
See below a sscce
package com.streamingout
import akka.http.scaladsl.model.ws.TextMessage
import akka.http.scaladsl.server.Directives._
import akka.http.scaladsl.server.PathMatchers.Rest
import akka.http.scaladsl.testkit.{ScalatestRouteTest, WSProbe}
import akka.stream.scaladsl.{Flow, Sink, Source}
import org.scalatest.{FlatSpec, Matchers}
class Test extends FlatSpec with Matchers with ScalatestRouteTest{
//--------------- Flow ---------------
def flow = {
import scala.concurrent.duration._
val source = Source.tick(initialDelay = 0 second, interval = 1 second, tick = TextMessage("tick"))
Flow.fromSinkAndSource(Sink.ignore, source)
}
//-------------- Routing ------------
def route = {
path("/wskt") {
println("websocket ws")
handleWebSocketMessages(flow)
} ~
path(Rest) { pathRest =>
println("path Rest")
getFromFile(s"webapp/$pathRest")
}
}
// create a testing probe representing the client-side
val wsClient = WSProbe()
// WS creates a WebSocket request for testing
WS("/wskt", wsClient.flow) ~> route ~> check {
// check response for WS Upgrade headers
isWebSocketUpgrade shouldEqual true
}
}
When I run the test, I can see in my console the path Rest message, meaning that WS doesnt upgrade to Websocket.
Anyone knows what is wrong with my code?
I am using akka 2.4.7
Thank you
To make the above code work, in the route, the path /wkst should be without any leading slash
def route = {
path("wskt") {
println("websocket ws")
handleWebSocketMessages(flow)
} ~
path(Rest) { pathRest =>
println("path Rest")
getFromFile(s"webapp/$pathRest")
}
}
Gatling Newbie here. I am trying to check the html of my page using the below documented css selector. The css item is present on the page (verified with postman) but Gatling is not finding it. I receive a variable undefined error on compile. Any advice would be greatly appreciated.
import scala.concurrent.duration._
import io.gatling.core.Predef._
import io.gatling.http.Predef._
import io.gatling.jdbc.Predef._
class LoggingIn extends Simulation {
val httpProtocol = http
.baseURL("https://test.spidastudio.com")
.inferHtmlResources(BlackList(""".*\.js""", """.*\.css""", """.*\.gif""", """.*\.jpeg""", """.*\.jpg""", """.*\.ico""", """.*\.woff""", """.*\.(t|o)tf""", """.*\.png"""), WhiteList())
val headers_0 = Map(
"User-Agent"-> "Gatling",
"Accept" -> "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Accept-Encoding" -> "gzip, deflate",
"Cache-Control" -> "max-age=0",
"Origin" -> "https://test.spidastudio.com",
"Upgrade-Insecure-Requests" -> "1")
val headers_1 = Map("User-Agent"-> "Gatling","X-Requested-With" -> "XMLHttpRequest")
val uri1 = "https://test.spidastudio.com"
val scn = scenario("LoggingIn")
.exec(http("REQUEST A")
.post("/cas/login?service=https%3A%2F%2Ftest.spidastudio.com%2Fprojectmanager%2Fj_spring_cas_security_check")
//Defining variable LTValue
.check(css("input[name=lt]", "value").saveAs("LTValue"))
)
.exec(http("request_0")
.post("/cas/login?service=https%3A%2F%2Ftest.spidastudio.com%2Fprojectmanager%2Fj_spring_cas_security_check")
.headers(headers_0)
.formParam("username", "xxxxxxx.com")
.formParam("password", "xxxxxxxx")
//Calling variable
.formParam("lt", "${LTValue}")
Have you tried by removing this line from your code?
.inferHtmlResources(BlackList(""".*\.js""", """.*\.css""", """.*\.gif""", """.*\.jpeg""", """.*\.jpg""", """.*\.ico""", """.*\.woff""", """.*\.(t|o)tf""", """.*\.png"""), WhiteList())
or remove *.css""" ? Works?
I can't seem to completely destroy my Backbone.Views. I have tried all the suggestions and still they turn into zombies and start an apocalypse on my memory. Please tell me what I am doing wrong.
When I toggle back and forth between routes /access and /settings the browser creates new instances as expected, the close method fires and completes, however the profiler in chrome says something the previous instances are hanging around.
My BaseView
class BaseView extends Backbone.View
# -------------------------------------------
isRendered: false
autoDestroy: false
# -------------------------------------------
close: ->
log 'Closing View', #, #model
#onClose() if #onClose?
#remove() if #?
#unbind() if #? # Unbind all local event bindings
delete #$el # Delete the jQuery wrapped object variable
delete #el # Delete the variable reference to this node
delete #
log 'Everything is destroyed', #
# -------------------------------------------
module.exports = BaseView
My Router
class Router extends BaseRouter
name: "Router"
container: "#content-container"
routes: routes
#----------------------
access: (slug) ->
AccessPage = require '../view/page/access-page.coffee'
accessPage = new AccessPage()
accessPage.render()
#showPage accessPage
#----------------------
settings: (slug) ->
SettingsPage = require '../view/page/settings-page.coffee'
settingsPage = new SettingsPage()
settingsPage.render()
#showPage settingsPage
My BaseRouter
class BaseRouter extends Backbone.Router
currentPageView: null
pageClass: '.page'
# -------------------------------------------
showPage: (view, hidePages = true) ->
#hideAllPages() if hidePages
#currentPageView = view
$(#container).append view.$el
log 'showPage', view.$el
view.$el.show()
# -------------------------------------------
hideAllPages: ->
if #currentPageView?.onHide?
#currentPageView.onHide()
if #currentPageView? and #currentPageView.autoDestroy
#currentPageView.close()
$(#pageClass).hide()
One of my Views
class AccessPage extends BaseView
name: "AccessPage"
id: "access-page"
autoDestroy: true
className: 'page'
#----------------------
initialize: ->
log #name, "initialize"
#----------------------
render: ->
log #name, "render"
#$el.html template
title: "Access"
#isRendered = true
#----------------------
module.exports = AccessPage
I wanted to allow any routes which start with # and any number
Example :
http://127.0.0.1/mypage/#435fsdfd
This should basically execute renderMyPage.
Tried below things but didn't work
routes : {
"" : "renderMyPage",
"#:/" : "renderMyPage"
}
A few things:
If your application is not being served from the root url / of your domain, you need to tell History where the root really is
Backbone.history.start({root: "/mypage/"});
you don't need to define the hash in the routes, if you just want to match 'something' on the hash this is enough:
routes : {
"" : "renderMyPage",
":value" : "renderMyPage"
},
renderMyPage: function (value) {
}
in the latest backbone (0.9.10) you can use optional arguments so you can do everything in one route
routes : {
"(:value)" : "renderMyPage"
},