Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Hh rm mixins #245

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 58 additions & 3 deletions src/ReactiveTools.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import Genie
import Stipple: deletemode!, parse_expression!, init_storage

# definition of variables
export @readonly, @private, @in, @out, @jsfn, @readonly!, @private!, @in!, @out!, @jsfn!, @mixin
export @readonly, @private, @in, @out, @jsfn, @readonly!, @private!, @in!, @out!, @jsfn!, @mixin, @mixins

#definition of handlers/events
export @onchange, @onbutton, @event, @notify
Expand Down Expand Up @@ -532,6 +532,56 @@ macro mixin(location, expr, prefix, postfix)
end |> esc
end

"""
@mixins [DemoMixin1, DemoMixin2]

Add one or more ReactiveModels as mixin to the app. The mixins need to be passed as a Vector.
This feature is meant to be able to design functionalities that can be reused within other apps.

All fields, client data, and js functions like watchers, life cycle hooks, etc are automatically embedded.

However, the recommended way of embedding the fields
is via the `@mixin` in the app definition, because only then they are automatically synchronised according to their definition.
So in order to include a mixin with data and functions, two steps are needed.

This feature is still tentative and might be redesigned in the future.
```julia
@app GreetMixin begin
@in name = "John Doe"
end

@mounted GreetMixin = "console.log('Just mounted the App including the GreetMixin')"

@methods GreetMixin \"\"\"
greet: function() { console.log('Hi ' + this.name + '!') }
\"\"\"

@mixins [GreetMixin]

@app begin
@mixin GreetMixin
@in s = "Hi"
@in i = 10
end

ui() = btn("Say Hello", @click("greet"))

@page("/", ui())

up(open_browser = true)
"""
macro mixins(expr)
esc(quote
let M = Stipple.@type
Stipple.ReactiveTools.@mixins M $expr
end
end)
end

macro mixins(App, mixins)
:(Stipple.get_mixins(::Type{<:$App}) = Type{<:ReactiveModel}[$mixins...]) |> esc
end

#===#

function init_handlers(m::Module)
Expand Down Expand Up @@ -606,8 +656,13 @@ macro init(args...)
identity
end
end
instance = let model = initfn($(init_args...))
new_handlers ? Base.invokelatest(handlersfn, model) : handlersfn(model)
instance = initfn($(init_args...))
# append eventhandlers
new_handlers ? Base.invokelatest(handlersfn, instance) : handlersfn(instance)
# append eventhandlers of mixins
for mixin in Stipple.get_mixins(instance)
handlers = get(Stipple.ReactiveTools.HANDLERS_FUNCTIONS, mixin, nothing)
handlers === nothing || handlers(instance)
end
for p in Stipple.Pages._pages
p.context == $__module__ && (p.model = instance)
Expand Down
9 changes: 9 additions & 0 deletions src/Stipple.jl
Original file line number Diff line number Diff line change
Expand Up @@ -761,6 +761,14 @@ function injectdeps(output::Vector{AbstractString}, M::Type{<:ReactiveModel}) ::
startswith("$key", model_prefix) && push!(output, f()...)
end
end
for mixin in get_mixins(M)
modelfields = fieldnames(Stipple.get_concrete_type(M))
mixinfields = fieldnames(Stipple.get_concrete_type(mixin))
# if all fields are already part of the model, don't include data
mode = isempty(setdiff(mixinfields, modelfields)) ? :mixindeps : :mixin
out = mixin_dep(mixin; mode)()
out === nothing || push!(output, out)
end
output
end

Expand Down Expand Up @@ -809,6 +817,7 @@ function deps!(M::Type{<:ReactiveModel}, f::Function; extra_deps = true)
end

deps!(M::Type{<:ReactiveModel}, modul::Module; extra_deps = true) = deps!(M, modul.deps; extra_deps)
deps!(M::Type{<:ReactiveModel}, mixin::ReactiveModel; extra_deps = true) = deps!(M, mixin_deps(mixin); extra_deps)

deps!(m::Any, v::Vector{Union{Function, Module}}) = deps!.(Ref(m), v)
deps!(m::Any, t::Tuple) = [deps!(m, f) for f in t]
Expand Down
41 changes: 34 additions & 7 deletions src/stipple/rendering.jl
Original file line number Diff line number Diff line change
Expand Up @@ -119,10 +119,11 @@ jsrender(r::Reactive, args...) = jsrender(getfield(getfield(r,:o), :val), args..

Renders the Julia `ReactiveModel` `app` as the corresponding Vue.js JavaScript code.
"""
function Stipple.render(app::M)::Dict{Symbol,Any} where {M<:ReactiveModel}
result = Dict{String,Any}()

for field in fieldnames(typeof(app))
function Stipple.render(app::M; mode::Symbol = :vue)::LittleDict{Symbol,Any} where {M<:ReactiveModel}
result = LittleDict{String,Any}()
ff = collect(fieldnames(typeof(app)))
mode == :vue || setdiff!(ff, [:channel__, :modes__], Stipple.AUTOFIELDS)
for field in ff
f = getfield(app, field)

occursin(SETTINGS.private_pattern, String(field)) && continue
Expand All @@ -131,9 +132,17 @@ function Stipple.render(app::M)::Dict{Symbol,Any} where {M<:ReactiveModel}
result[julia_to_vue(field)] = Stipple.jsrender(f, field)
end

vue = Dict( :el => JSONText("rootSelector"),
:mixins => JSONText("[watcherMixin, reviveMixin, eventMixin]"),
:data => merge(result, client_data(app)))
vue = LittleDict{Symbol, Any}()
mixin_names = Symbol[nameof(m) for m in get_mixins(app)]

if mode == :vue
push!(vue, :el => JSONText("rootSelector"))
mixin_names = vcat([:watcherMixin, :reviveMixin, :eventMixin], mixin_names)
end

isempty(mixin_names) || push!(vue, :mixins => JSONText("[$(join(mixin_names, ", "))]"))
mode == :mixindeps || push!(vue, :data => merge(result, client_data(app)))

for (f, field) in ((components, :components), (js_methods, :methods), (js_computed, :computed), (js_watch, :watch))
js = join_js(f(app), ",\n "; pre = strip)
isempty(js) || push!(vue, field => JSONText("{\n $js\n}"))
Expand All @@ -151,6 +160,24 @@ function Stipple.render(app::M)::Dict{Symbol,Any} where {M<:ReactiveModel}
vue
end

function get_mixins(::Type{<:ReactiveModel})::Vector{Type{<:ReactiveModel}}
Type{ReactiveModel}[]
end

function get_mixins(app::ReactiveModel)::Vector{Type{<:ReactiveModel}}
get_mixins(get_abstract_type(typeof(app)))
end

function mixin_dep(Mixin::Type{<:ReactiveModel}; mode::Symbol = :mixindeps)
mix = strip(json(render(Mixin(); mode)), '"')
mixin_dep() = if mix == "{}"
nothing
else
script("const $(nameof(Mixin)) = $mix\n")
end
end


"""
function Stipple.render(val::T, fieldname::Union{Symbol,Nothing} = nothing) where {T}

Expand Down
Loading