PivotTableUI in clojurescript from react-pivottable on change interface not render new status - pivot-table

The component is loaded correctly with the initial data, but the data does not change when the state is changed from the interface, what can I be doing wrong?
(ns ui.pivottable.react-pivottable
(:require ["react-pivottable/PivotTableUI" :default PivotTableUI]
["react-pivottable/TableRenderers" :as TableRenderers]
["react-plotly.js" :default Plot]
["react-pivottable/PlotlyRenderers" :as create-plotly-renderers]
[reagent.core :as r]))
(defn pivottable []
(let [state (r/atom {:data (clj->js [[:name "Alice" :age 30 :city "New York"]
[:name "Bob" :age 35 :city "Chicago"]
[:name "Charlie" :age 40 :city "New York"]])
:aggregator-name "Count"
:renderer-name "Table"
:rows [:name]
:cols [:age]})
component (r/adapt-react-class PivotTableUI)
plotly-renderers (create-plotly-renderers Plot)
table-renderers TableRenderers
params {:rows [:name]
:cols [:age]
:on-change #(do (reset! state (.assign js/Object #js {} %))
;; (reset! state (clj->js %) another try, it's the same)
(.log js/console #state))
:renderers (.assign js/Object #js {} plotly-renderers table-renderers)}]
[component (merge #state params)]))

Diego. You are using let instead of r/with-let. Therefore, when you change the state of the atom the component is rendered and the atom is initialised with the data again.

Related

SwiftUI - Using an Array of Ints that is wrapped with #Binding

I gather that the #Binding wrapper is used when a parent view and a child view both need to share a variable without violating the principle of having “a single source of truth”. This isn’t working as I expect when the variable is an Array. Essentially, the data that the child view assigns to this bound variable gets dropped so that the parent view sees an empty array.
The attached example is taken from the Developer Documentation (under XCode’s Help menu) for the #Binding wrapper. Apple’s code shows a simple video controller. It allows a user to play or pause videos (videos are stored as Episode structs). The child view is named PlayButton and toggles between a right-arrow (start) and a double-bar (stop). In Apple’s code the child view had a single variable wrapped with #Binding, which was the isPlaying Boolean variable. When the play button is tapped it toggles the isPlaying variable. As you might expect, the orginal code worked fine.
To demonstrate the problem I’ve modified the child view so that now accepts an array of Episodes. Please assume that the child view must report the size of the show titles to the parent view without violating the single source of truth principle. As a consequence of this need, there is also a new #Binding-wrapped array that records the number-of-characters in a show’s title (for each of the shows).
Additionally, the child view now shows Text Views reporting the show title of each Episode and the title’s size, just to prove that the code is being executed. The parent View should then be able to display the number of elements in its #State array, sizeOfShowTitles. As coded here, I expect the number of episodes to be 1. Instead, the parent view is reporting that sizeOfShowTitles has zero elements.
The playground contains just these 4 elements:
Episode (a struct that identifies the videos)
PlayButton (the child View)
PlayerView (the parent View)
the PlaygroundPage.current.setLiveView(PlayerView()) command, used to excuted SwiftUI in playgrounds.
Can anyone comment on why assignment of values to the bound array is failing?
Note 1:
The problem does not seem to lie with the .append function used in the child view. If you replace the append function with a simple assignment then the outcome is the same - there are no elements in the array in the parent View (tested, but not shown here).
Note 2.
As shown in Apples code (and retained here), assigning a value to a Boolean Type works as expected. So, it appears that the problem has something to do with the Array Type.
Note 3.
I’m using Swift 5.5 in XCode 5.4.1.
// Playground used to show the issue with saving a
// value to an array that is wrapped with #Binding
import SwiftUI
import PlaygroundSupport
// HELPER Struct: records metadata on shows that can be played in PlayerView
struct Episode: Identifiable {
var id = UUID()
var title = "Title"
var showTitle = "Show Title"
}
// CHILD View
// String Array)
struct PlayButton: View {
// input API
#Binding var isPlaying: Bool
var episodes: [ Episode ]
// output API
#Binding var charactersInShowTitle: [ Int ]
var body: some View {
Button(action: {
self.isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
ForEach( episodes ) { episode in
Text( "CHILD: \( analyzeShowTitle( episode) )" )
}
}
func analyzeShowTitle( _ episode: Episode ) -> String {
let characterCount = episode.showTitle.count
charactersInShowTitle.append( characterCount )
return "\( episode.showTitle ) - \( characterCount ) chars"
}
}
// PARENT
// (modified to show the list of sizes from a String Array)
struct PlayerView: View {
let episodes = [ Episode(title: "Title 1",
showTitle: "Show 1"),
Episode( title: "Title 1",
showTitle: "Show 21")
]
#State private var isPlaying: Bool = false
#State private var sizeOfShowTitles = [ Int ]()
var body: some View {
VStack {
Text("Player App")
.foregroundStyle(isPlaying ? .primary : .secondary)
Text("")
PlayButton(isPlaying: $isPlaying,
episodes: episodes,
charactersInShowTitle: $sizeOfShowTitles )
Text("")
Text( "PARENT no. elements: \( $sizeOfShowTitles.wrappedValue.count)"
)
}
.frame(width: 300)
}
}
PlaygroundPage.current.setLiveView(PlayerView())
How about trying something like this example code,
using .onAppear{...} for updating each episode with the showTitle.count.
Also removing this "appending" from analyzeShowTitle function. This append(...) should not be used within the ForEach loop, because it triggers a view refresh,
which then triggers another append(...) etc...
struct PlayButton: View {
// input API
#Binding var isPlaying: Bool
var episodes: [Episode]
// output API
#Binding var charactersInShowTitle: [Int]
var body: some View {
Button(action: {
isPlaying.toggle()
}) {
Image(systemName: isPlaying ? "pause.circle" : "play.circle")
}
ForEach(episodes) { episode in
Text("CHILD: \(analyzeShowTitle(episode))")
}
.onAppear { // <-- here
for episode in episodes {
charactersInShowTitle.append(episode.showTitle.count)
}
}
}
func analyzeShowTitle( _ episode: Episode ) -> String {
return "\( episode.showTitle ) - \( episode.showTitle.count ) chars" // <-- here
}
}

Call component method when model property changes

In my Fable app with Elmish I have a view that uses react-slick and a button that should be able to change the slide number on click:
Fable.Import.Slick.slider
[ InitialSlide model.SlideNumber
AfterChange (SlideTo >> dispatch) ]
children
Button.button
[ Button.OnClick (fun _ev -> dispatch (SlideTo 5)) ]
[ str "Go to slide 5" ]
The react component for the slider is defined by react-slick.
The fable wrapper I had to write on my own, so it's not complete because I only defined the properties I need.
module Fable.Import.Slick
open Fable.Core
open Fable.Core.JsInterop
open Fable.Helpers
open Fable.Helpers.React.Props
open Fable.Import.React
type SliderProps =
| InitialSlide of int
| AfterChange of (int -> unit)
interface IHTMLProp
let slickStyles = importAll<obj> "slick-carousel/slick/slick.scss"
let slickThemeStyles = importAll<obj> "slick-carousel/slick/slick-theme.scss"
let Slider = importDefault<ComponentClass<obj>> "react-slick/lib/slider"
let slider (b: IHTMLProp list) c = React.from Slider (keyValueList CaseRules.LowerFirst b) c
So while react-slick defines a property InitialSlide to set the slide that should initially be shown, there's no property to update the slide afterwards. There is a method slickGoTo that should do what I want. But I don't know how or where to call that method while still being compliant with Elmish.
I could imagine that I have to extend the react component and listen to the model property and then call slickGoTo whenever that property changes. But I don't know if that's possible.
So my question is: How can I have a button that changes the slide number on click using slickGoTo that is defined by the component while not hacking the Elmish architecture?
An alternative to storing the reference to the slider object in the model is to use a mutable variable:
let mutable sliderRef : Browser.Element option = None
let gotoSlide n =
match sliderRef with None ->
| None -> ()
| Some slider -> slider?slickGoTo n
the rest is similar:
type Msg =
| CurrentSlide of int
| GotoSlide of int
...
let update msg model =
match msg with
| CurrentSlide n -> { model with slideNumber = n } , []
| GotoSlide n -> gotoSlide n ; model, []
Create your slider like this:
slider
[ Ref (fun slider = sliderRef <- Some slider)
AfterChange (CurrentSlide >> dispatch)
InitialSlide 0
]
slides
To be able to call the method slickGoTo you need to get a reference to the slider object. To do that use the Ref prop that expects a function of type (Browser.Element -> unit). It gets called once. Save that element somewhere, perhaps in your model:
type Model = {
...
slideNumber : int
sliderRef : Browser.Element option
...
}
let gotoSlide sliderRef n =
match sliderRef with None ->
| None -> ()
| Some slider -> slider?slickGoTo n
That way you can call gotoSlide from your update function.
type Msg =
| SetSliderRef of Browser.Element
| CurrentSlide of int
| GotoSlide of int
...
let update msg model =
match msg with
| SetSliderRef e -> { model with sliderRef = Some e } , []
| CurrentSlide n -> { model with slideNumber = n } , []
| GotoSlide n -> gotoSlide model.sliderRef n ; model, []
...
Create your slide like this:
slider
[ Ref (SetSliderRef >> dispatch)
AfterChange (CurrentSlide >> dispatch)
InitialSlide 0
]
slides
I have not tested any of this, so take with a grain of salt.
I just found that within React you previously solved such things using componentWillReceiveProps - that sounds a lot like what I want IMO. But this is now obsolete and there are two recommendations based on my usage:
If you used componentWillReceiveProps to “reset” some state when a prop changes, consider either making a component fully controlled or fully uncontrolled with a key instead.
I don't think I can implement the Fully controlled version (this has to be done by the author of the component, right?), but the fully uncontrolled with a key seems to work. So my slider view now looks like this:
Fable.Import.Slick.slider
[ InitialSlide model.SlideNumber
AfterChange (SlideTo >> dispatch)
Key (string model.SlideNumber) ]
According to the docs everytime the Key changes, the component is recreated. Now this sounds like a lot of overhead and has its own drawbacks (animating the slide doesn't work) but currently it's the simplest solution. Any concerns?

Query an array in Firebase SWIFT

using swift and firebase I have a list of data like so
{
"posts" : {
"-KHbULJcKqt9bSpxNO9k" : {
"author" : "Michele",
"postText" : "tags as arrays?",
"tag" : [ "HELLO", "WHAT'S UP?", "TEST" ]
}
with many more post. I want to search by tag and if a post contains the search term as a tag return the whole post. Ive been able to do this with strings using queryEqualToValue: but haven't been able to solve it for an array. Any point in the right direction is appreciated thank you.
This is what my save function looks like
func createNewPost(post: Dictionary<String, AnyObject>, tags: [String]) {
let firebaseNewPost = POST_REF.childByAutoId()
let firebaseTag = Firebase(url: "\(POST_REF)/\(firebaseNewPost.key)/tag")
print(firebaseTag)
firebaseNewPost.setValue(post)
for tag in tags {
let tagID = firebaseTag.childByAutoId()
tagID.setValue(tag)
}
}
Now I need help with searching through the data and retrieving certain post based on what a user searches for. This is what I was using when it was just one tag and it was a just a string.
func loadTaggedShit(searchTerm: String) {
DataService.dataService.POST_REF.queryOrderedByChild("tag").queryEqualToValue(seachTerm).observeEventType(.ChildAdded, withBlock: { snapshot in
self.posts = []
if let snapshots = snapshot.children.allObjects as? [FDataSnapshot] {
for snap in snapshots {
if let postDictionary = snap.value as? Dictionary<String, AnyObject> {
let key = snap.key
let post = Post(key: key, dictionary: postDictionary)
self.posts.insert(post, atIndex: 0)
}
}
}
self.tableView.reloadData()
})
}
also the data now looks like this
"posts":{
"-KHj_bDJmZJut7knKoUX" : {
"author" : "Michele",
"postText" : "what's up",
"tag" : {
"-KHj_bDJmZJut7knKoUY" : "HEY"
}
}
}
Firebase arrays are very situational and really should be avoided if possible.
Array's in code are very powerful data constructs but they just don't work well within a JSON structure. If they are modified, say, removing a node, that index will be removed as well. That leaves the array with 'holes' e.g. 0, 1, 2, 4, 5, 7 etc. So, create yourself a tags child node within your post node, and then child nodes with keys created with childByAutoId
"posts" : {
"-KHbULJcKqt9bSpxNO9k" : {
"author" : "Michele",
"postText" : "tags as arrays?",
"tags"
tag_id_0
tag_title: "HELLO"
tag_id_1
tag_title: "WHAT'S UP?"
tag_id_2
tag_title: "TEST"
}
You might even consider creating a separate tags node and reference it within the posts node - especially if they are going to be re-usable.
Edit:
Based on some additional information, a different structure will be in order because the OP needs to query for posts that contain a specific tag and the tags are in fact reusable and not specific to a post:
posts
-KHbULJcKqt9bSpxNO9k
author: "Michele"
postText: "tags as arrays?"
tags
tag_id_1: true
tag_id_2: true
-JPHJSojo91920LLK0J
author: "Larry"
postText: "NO, don't use arrays"
tags
tag_id_2: true
tags
tag_id_0
tag_title: "HELLO"
tag_id_1
tag_title: "WHAT'S UP?"
tag_id_2
tag_title: "TEST"
And then the code to receive all posts that use the TEST tag, tag_id_2
This is called a Firebase Deep Query
postsRef.queryOrderedByChild("tags/tag_id_2").queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot)
})

Netzke change form header title

def preconfigure_record_window(c)
super
c.header = true
c.width = 600
c.draggable = false
c.title = "New Title"
end
In this code I want to change the form title. Whatever I am trying it is displaying the default title Add
You'd need to define it separately for add and edit actions:
component :add_window do |c|
super(c)
c.title = "New title - add"
...
end
component :edit_window do |c|
super(c)
c.title = "New title - edit"
...
end

Backbone listener doesn't appear to be firing

I've got a copule of files that I'm working with and I'm trying to create a listener on this method I have in my backbone view
appView.js.coffee
namespace "happiness_kpi", (exports) ->
exports.appView = Backbone.View.extend
events:
"click #happy" : "selection"
selection: ->
console.log "selection was called"
index.html.haml
%input{ :type => "image", :src => "/assets/smiley.jpg", :alt => "happy", :id => "happy" }
%input{ :type => "image", :src => "/assets/undecided.jpg", :alt => "undecided", :id => "undecided" }
%input{ :type => "image", :src => "/assets/sad.jpg", :alt => "sad", :id => "sad" }
and here is my spec:
app_view_spec.js.coffee
it "is called when one of the faces is clicked", ->
$("body").append('<input alt="happy" id="happy" src="/assets/smiley.jpg" type="image">')
$("body").append('<input alt="undecided" id="undecided" src="/assets/undecided.jpg" type="image">')
$("body").append('<input alt="sad" id="sad" src="/assets/sad.jpg" type="image">')
#subject.selection = sinon.spy()
$("#happy").click
sinon.assert.calledOnce(#subject.selection)
I'm getting the error 'Error: expected spy to be called once but was called 0 times' Anyone have any ideas as to why the event is not being triggered when the input is clicked?
Thanks in advance.
If the problem is not the obvious typo of the brackets when calling click() then the other thing I'm thinking is that the events in Backbone are scoped within the View element. That means that a View cannot list to events happening outside itself (it can, of course, but not by simply using the events object).
Try doing this at the beginning of your test:
#subject.setElement($("body"));
This will set the View el and $el to the body tag, so the images you are appending are actually going inside your View and the events will be triggered.
So after much research and tinkering, I seem to have come to an answer. The problem was definitely the way that I was appending the images to the iframe. Problem was that the test seemed to be creating a new element outside of the iframe, and was not visible to the page. The $('#happy').click() was executing correctly, however, it was executing on something that didn't exist in the iframe.
So my solution:
app_view_spec.js.coffee
beforeEach ->
$("body").append('<div id="display"></div>')
afterEach ->
$("body").append('<div id ="display"></div>').remove()
it "is called when one of the faces is clicked", ->
#subject.emotionSelection = sinon.spy()
#subject.delegateEvents()
$("input#happy").click()
sinon.assert.calledOnce #subject.emotionSelection
appView.js.coffee
el: '#display'
initialize: ->
#render()
events: ->
'click #happy': #emotionSelection
render: ->
#$el.html HandlebarsTemplates.faces()
emotionSelection: ->
console.log "hello"
I found doing it this way was a much nicer approach, I just appended my HAML to the iframe, rather than trying to append each image on its own. Lesson learned. Thanks for the help!

Resources