jakub-zawislak / formex Goto Github PK
View Code? Open in Web Editor NEWA better form library for Phoenix
Home Page: https://hexdocs.pm/formex
License: MIT License
A better form library for Phoenix
Home Page: https://hexdocs.pm/formex
License: MIT License
What is the way to make one group of fields for adding new collection item to show up without need to click on the Add button?
Need support for input_helpers for customizing field inputs or adding support for custom inputs like arrays, maps.
I want to add fields like date_select, time_select, datetime_select in formex? And where can I find all available for formex input types in the documentation?
Currently, there is no way for creating new field inputs in the template directly rather everything has to go through type.ex file.
I suppose create_field can be used directly but no clear documentation on that.
Hey there!
I've run into this issue a couple times now, where @repo Application.get_env(:formex, :repo)
evaluates to nil
, which causes issues like ** (UndefinedFunctionError) function nil.preload/2 is undefined or private
. So far I've been able to get things back on track by running mix deps.compile --force
, but it doesn't seem like that's something that should have to be done occasionally.
I'll keep my eyes open to see if I can't find out how I'm causing this to happen.
Is there any way we can use selectassoc field type inside embeds
Like select element for posts inside embeds?
formex/web/static/js/formex.js
Line 38 in 0d47947
Is there a way to make an input hidden?
I'm looking to set a boolean in a nested form. I'm using custom_value
to conditionally set it, but I'd like it hidden as well. I can do it with CSS but I thought there should be some better way.
Any thoughts to adding EEx support when creating a custom Formex template?
Hi. I love your library! It makes form building incredibly fun and easy.
One a project of mine, I'm using the Money library for some of my columns. For example:
schema "campaigns" do
field :name, :string
field :bid_amount_cents, Money.Ecto.Type
end
When I set up the type, it looks like this:
defmodule MyWeb.CampaignType do
use Formex.Type
def build_form(form) do
form
|> add(:name, :text_input, label: "Name", validation: [presence: true])
|> add(:bid_amount_cents, :number_input, label: "Bid Amount", validation: [presence: true])
|> add(:save, :submit, label: "Submit", phoenix_opts: [
class: "btn-primary"
])
end
end
My issue is that the form field that it renders does not use the amount value of the bid_amount_cents. Right now when it gets the value, it uses @campaign.bid_amount_cents
when I need it to use @campaign.bid_amount_cents.amount
.
Is there a way to specify the value in the CampaignType
above where I can derive the value as a function call? I assume this is possible with a custom field, but the docs are pretty shaky around custom fields and I can't find any good examples anywhere.
Thanks for your help!
I have an array column with roles {:array, :string} => ["admin", "developer", "sponsor"]
I would like to have a checkbox for each one and if checked, they update the array with the content. Is that possible with Formex?
When you send form with invalid select
value, i.e. that value wasn't available in options of select, formex raises error. We don't want exceptions, it should simply display error in the form.
My schema is:
defmodule Hello.Hr.Resume do
use Ecto.Schema
import Ecto.Changeset
alias Hello.Hr.{Resume, Data, Welder, Experience}
schema "resumes" do
embeds_one :data, Data, on_replace: :update
field :birthday, :date
field :full_name, :string
field :joined_at, :naive_datetime
field :next_event, :utc_datetime
field :start_work_time, :time
timestamps()
end
@doc false
def changeset(resume, attrs) do
resume
|> cast(attrs, [:full_name, :birthday, :joined_at, :next_event, :start_work_time])
|> validate_required([:full_name, :birthday, :joined_at, :next_event, :start_work_time])
|> cast_embed(:data)
end
The type is:
defmodule Hello.Hr.ResumeType do
use Formex.Type
use Formex.Ecto.Type
use Formex.Ecto.ChangesetValidator
alias Hello.Hr.{Data, DataType}
def build_form(form) do
form
|> add(:full_name, :text_input, [:required])
|> add(:birthday, :date_select)
|> add(:joined_at, :datetime_select)
|> add(:next_event, :datetime_select)
|> add(:start_work_time, :time_select)
|> add(:data, DataType, struct_module: Data)
|> add(:save, :submit)
end
end
In my controller I have a create function:
def create(conn, %{"resume" => resume_params}) do
ResumeType
|> create_form(%Resume{}, resume_params)
|> handle_form
|> case do
{:ok, resume} ->
resume = Hello.Repo.insert!(resume)
redirect(conn, to: Routes.resume_path(conn, :show, resume))
{:error, form} ->
render(conn, "new.html", form: form)
end
end
and in the form template:
<%= formex_form_for @form, @action, fn f -> %>
<%= if @form.submitted? do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= formex_row f, :full_name %>
<%= formex_row f, :birthday %>
<%= formex_row f, :joined_at %>
<%= formex_row f, :next_event %>
<%= formex_row f, :start_work_time %>
<%= formex_nested f, :data, fn fd -> %>
<%= formex_nested fd, :welder, fn fw -> %>
<%= formex_row fw, :vertical %>
<%= formex_row fw, :horizontal %>
<% end %>
<%= formex_collection fd, :experiences %>
<% end %>
<%= formex_row f, :save %>
<% end %>
When I try to insert new resume I get:
** (exit) an exception was raised:
** (Ecto.ChangeError) value `%{"day" => "2", "month" => "3", "year" => "2014"}` for `Hello.Hr.Resume.birthday` in `insert` does not
match type :date
My embedded schemas look like this:
embedded_schema do
field :city
field :position
field :start_date, :date
field :end_date, :date
formex_collection_child() # <- this is added
end
After saving formex form with nested form and with form collections presented by embedded schemas I got such struct from the database:
%Hello.Hr.Data{
experiences: [
%Hello.Hr.Experience{
city: "Kiev",
end_date: ~D[2016-10-10],
formex_delete: nil,
formex_id: nil,
id: "1191a0ce-73f5-4a44-868a-e58f4a71ff2c",
position: "Chef",
start_date: ~D[2014-04-15]
},
%Hello.Hr.Experience{
city: "Lviv",
end_date: ~D[2019-02-01],
formex_delete: nil,
formex_id: nil,
id: "c230e653-8d15-4dd0-a12d-8498d6f92a3f",
position: "Master",
start_date: ~D[2016-11-01]
}
],
formex_delete: nil,
formex_id: nil,
id: "a50c6e8e-be35-4028-99f6-4744dbd439f6",
welder: %Hello.Hr.Welder{
formex_delete: nil,
formex_id: nil,
horizontal: true,
id: "07a3a136-a6f2-4910-94f4-eca5507183da",
vertical: false
}
}
You see all embedded structs polluted by formex_id and formex_delete key value pairs.
I have a 2 level deep nested form of resume with nested data field presented by embedded schema and inside of data there are embeds_one (nested form in formex's terms) welder schema and embeds_many experiences schemas (collection form in formex's terms).
The Add button for adding a collection of experiences is present in the form but doesn't work.
My installation:
Fresh new phoenix 1.4
mix.exs:
def application do
[
mod: {Hello.Application, []},
extra_applications: [:logger, :runtime_tools, :formex]
]
end
defp deps do
[
{:phoenix, "~> 1.4.0"},
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"},
{:ecto_sql, "~> 3.0"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.11"},
{:phoenix_live_reload, "~> 1.2", only: :dev},
{:gettext, "~> 0.11"},
{:jason, "~> 1.0"},
{:plug_cowboy, "~> 2.0"},
{:pow, "~> 1.0.1"},
{:formex, "~> 0.6.0"},
{:formex_ecto, "~> 0.2.0"}
]
end
Schemas:
defmodule Hello.Hr.Resume do
use Ecto.Schema
import Ecto.Changeset
alias Hello.Hr.{Resume, Data, Welder, Experience}
schema "resumes" do
embeds_one :data, Data, on_replace: :update
field :birthday, :date
field :full_name, :string
field :joined_at, :naive_datetime
field :next_event, :utc_datetime
field :start_work_time, :time
timestamps()
end
@doc false
def changeset(resume, attrs) do
resume
|> cast(attrs, [:full_name, :birthday, :joined_at, :next_event, :start_work_time])
|> validate_required([:full_name, :birthday, :joined_at, :next_event, :start_work_time])
|> cast_embed(:data)
end
defmodule Hello.Hr.Data do
use Ecto.Schema
use Formex.Ecto.Schema
import Ecto.Changeset
alias Hello.Hr.{Welder, Experience}
embedded_schema do
embeds_one :welder, Welder, on_replace: :update
embeds_many :experiences, Experience, on_replace: :delete
formex_collection_child()
end
@doc false
def changeset(data, attrs) do
data
|> cast(attrs, [])
|> cast_embed(:welder, required: true)
|> cast_embed(:experiences, required: true)
end
end
defmodule Hello.Hr.Welder do
use Ecto.Schema
import Ecto.Changeset
use Formex.Ecto.Schema
embedded_schema do
field :vertical, :boolean, default: false
field :horizontal, :boolean, default: false
formex_collection_child()
end
def changeset(welder, attrs) do
welder
|> cast(attrs, [:vertical, :horizontal])
end
end
defmodule Hello.Hr.Experience do
use Ecto.Schema
import Ecto.Changeset
use Formex.Ecto.Schema
embedded_schema do
field :city
field :position
field :start_date, :date
field :end_date, :date
formex_collection_child()
end
def changeset(experience, attrs) do
experience
|> cast(attrs, [:city, :position, :start_date, :end_date])
|> validate_required([:city, :position, :start_date, :end_date])
end
end
Types:
defmodule Hello.Hr.ResumeType do
use Formex.Type
use Formex.Ecto.Type
use Formex.Ecto.ChangesetValidator
alias Hello.Hr.{Data, DataType}
def build_form(form) do
form
|> add(:full_name, :text_input, [:required])
|> add(:birthday, :date_select)
|> add(:joined_at, :datetime_select)
|> add(:next_event, :datetime_select)
|> add(:start_work_time, :time_select)
|> add(:data, DataType, struct_module: Data)
|> add(:save, :submit)
end
end
defmodule Hello.Hr.DataType do
use Formex.Type
use Formex.Ecto.Type
use Formex.Ecto.ChangesetValidator
alias Hello.Hr.{Welder, WelderType, Experience, ExperienceType}
def build_form(form) do
form
|> add(:welder, WelderType, struct_module: Welder)
|> add(:experiences, ExperienceType, struct_module: Experience)
end
end
defmodule Hello.Hr.Welder do
use Ecto.Schema
import Ecto.Changeset
use Formex.Ecto.Schema
embedded_schema do
field :vertical, :boolean, default: false
field :horizontal, :boolean, default: false
formex_collection_child()
end
def changeset(welder, attrs) do
welder
|> cast(attrs, [:vertical, :horizontal])
end
end
defmodule Hello.Hr.ExperienceType do
use Formex.Type
use Formex.Ecto.Type
use Formex.Ecto.ChangesetValidator
def build_form(form) do
form
|> add(:city, :text_input)
|> add(:position, :text_input)
|> add(:start_date, :date_select)
|> add(:end_date, :date_select)
end
end
Controller:
defmodule HelloWeb.ResumeFormexController do
use HelloWeb, :controller
alias Hello.Hr.{Resume, ResumeType}
def new(conn, params) do
form = create_form(ResumeType, %Resume{})
render(conn, "new.html", form: form)
end
end
Templates:
new.html.eex:
<h1>New Resume</h1>
<%= render "form.html", Map.put(assigns, :action, Routes.resume_formex_path(@conn, :create)) %>
<span><%= link "Back", to: Routes.resume_formex_path(@conn, :index) %></span>
form.html.eex:
<%= formex_form_for @form, @action, fn f -> %>
<%= if @form.submitted? do %>
<div class="alert alert-danger">
<p>Oops, something went wrong! Please check the errors below.</p>
</div>
<% end %>
<%= formex_row f, :full_name %>
<%= formex_row f, :birthday %>
<%= formex_row f, :joined_at %>
<%= formex_row f, :next_event %>
<%= formex_row f, :start_work_time %>
<%= formex_nested f, :data, fn fd -> %>
<%= formex_nested fd, :welder, fn fw -> %>
<%= formex_row fw, :vertical %>
<%= formex_row fw, :horizontal %>
<% end %>
<%= formex_collection fd, :experiences %>
<% end %>
<%= formex_row f, :save %>
<% end %>
Javascript
package.json:
"dependencies": {
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"formex": "file:../deps/formex"
},
app.js:
import {Collection} from '../../deps/formex'
The html of the form generated by formex looks like this (cut select's options for brevity):
<form accept-charset="UTF-8" action="/resumes-formex" method="post"><input name="_csrf_token" type="hidden" value="FAMhQQ0IAj9LJWUvGBl4YQIMfmcZEAAACknp9EWpsaJlsi0PJX+6xw=="><input name="_utf8" type="hidden" value="?"> <div class="form-group required"><label class="control-label col-sm-2" for="resume_full_name">full_name</label><div class="col-sm-10"><input class="form-control " id="resume_full_name" name="resume[full_name]" type="text"></div></div>
<div class="form-group required"><label class="control-label col-sm-2" for="resume_birthday">birthday</label><div class="col-sm-10"><select id="resume_birthday_year" name="resume[birthday][year]">...</select></div></div>
<div class="form-group required"><label class="control-label col-sm-2" for="resume_next_event">next_event</label><div class="col-sm-10"><select id="resume_next_event_year" name="resume[next_event][year]">...</select></div></div>
<div class="form-group required"><label class="control-label col-sm-2" for="resume_data_welder_vertical">vertical</label><div class="col-sm-10"><div class="checkbox"><label><input name="resume[data][welder][vertical]" type="hidden" value="false"><input class="" id="resume_data_welder_vertical" name="resume[data][welder][vertical]" type="checkbox" value="true"></label></div></div></div> <div class="form-group required"><label class="control-label col-sm-2" for="resume_data_welder_horizontal">horizontal</label><div class="col-sm-10"><div class="checkbox"><label><input name="resume[data][welder][horizontal]" type="hidden" value="false"><input class="" id="resume_data_welder_horizontal" name="resume[data][welder][horizontal]" type="checkbox" value="true"></label></div></div></div>
<input id="resume_data_welder_id" name="resume[data][welder][id]" type="hidden" value=""> <div class="formex-collection" data-formex-prototype="<div class="formex-collection-items"><div class="formex-collection-item formex-collection-item-new" data-formex-index="0" style=""><a class="formex-collection-item-remove" data-formex-confirm="Are you sure?" href="#">&times;</a><div class="form-group required"><label class="control-label col-sm-2" for="resume[data]_experiences___idx0___city">city</label><div class="col-sm-10"><input class="form-control " id="resume[data]_experiences___idx0___city" name="resume[data][experiences][__idx0__][city]" type="text"></div></div><div class="form-group required"><label class="control-label col-sm-2" for="resume[data]_experiences___idx0___position">position</label><div class="col-sm-10"><input class="form-control " id="resume[data]_experiences___idx0___position" name="resume[data][experiences][__idx0__][position]" type="text"></div></div><div class="form-group required"><label class="control-label col-sm-2" for="resume[data]_experiences___idx0___start_date">start_date</label><div class="col-sm-10"><select id="resume[data]_experiences___idx0___start_date_year" name="resume[data][experiences][__idx0__][start_date][year]">...</select> / <select id="resume[data]_experiences___idx0___start_date_month" name="resume[data][experiences][__idx0__][start_date][month]">...</select> / <select id="resume[data]_experiences___idx0___start_date_day" name="resume[data][experiences][__idx0__][start_date][day]">...</select></div></div><div class="form-group required"><label class="control-label col-sm-2" for="resume[data]_experiences___idx0___end_date">end_date</label><div class="col-sm-10"><select id="resume[data]_experiences___idx0___end_date_year" name="resume[data][experiences][__idx0__][end_date][year]">...</select> / <select id="resume[data]_experiences___idx0___end_date_month" name="resume[data][experiences][__idx0__][end_date][month]">...</select> / <select id="resume[data]_experiences___idx0___end_date_day" name="resume[data][experiences][__idx0__][end_date][day]">...</select></div></div><input data-formex-id="" id="resume[data]_experiences___idx0___formex_id" name="resume[data][experiences][__idx0__][formex_id]" type="hidden"></div></div>"><div class="formex-collection-items"></div><div class="form-group"><div class="col-sm-10 col-sm-offset-2"><button class="btn btn-default formex-collection-add " type="button">Add</button></div></div></div>
<input id="resume_data_id" name="resume[data][id]" type="hidden" value=""> <div class="form-group"><div class="col-sm-10 col-sm-offset-2"><button class="btn btn-default " type="submit">save</button></div></div>
</form>
The formex's javascript library is also loaded in app.js, I've checked. And there are no errors in the browser console when page is loaded and when I click the Add button.
Can the modal and form use different field names / different structure?
defmodule App.Article do
defstruct [:name, :properties] # name vs title, :properties is a map with :content key
end
defmodule App.ArticleType do
use Formex.Type
def build_form(form) do
form
|> add(:title, :text_input)
|> add(:content, :textarea)
end
end
I am testing the many_to_many SelectAssoc for product to options many to many schema.
It is showing this error :
key :name not found in: %App.Option{meta: #Ecto.Schema.Metadata<:loaded, "options">, description: "opt1", factory: #Ecto.Association.NotLoaded, factory_id: 1, id: 6, inserted_at: #<DateTime(2017-04-18T02:00:39Z Etc/UTC)>, products: #Ecto.Association.NotLoaded, updated_at: #<DateTime(2017-04-18T02:00:39Z Etc/UTC)>}
It boils down to select_assoc.ex line 208 where row.name cannot be fetched from assoc.
This is just a question rather than issue.
Is there any documentation where I can find how to use embedded_many or embedded_one with formex.
Thank you for this module! It's awesome! :D
My controller looks like this:
defmodule HelloWeb.ResumeFormexController do
use HelloWeb, :controller
alias Hello.Hr.{Resume, ResumeType}
def index(conn, _params) do
resumes = Hr.list_resumes()
render(conn, "index.html", resumes: resumes)
end
def new(conn, params) do
form = create_form(ResumeType, %Resume{})
render(conn, "new.html", form: form)
end
def create(conn, params) do
ResumeType
|> create_form(%Resume{}, params)
|> handle_form
|> case do
{:ok, resume} ->
redirect(conn, to: Routes.resume_path(conn, :show, resume))
{:error, form} ->
render(conn, "new.html", form: form)
end
end
def show(conn, %{"id" => id}) do
resume = Hr.get_resume!(id)
inspect(resume)
render(conn, "show.html", resume: resume)
end
end
When I submit form I get:
iex(2)> [info] POST /resumes-formex
iex(2)> [debug] Processing with HelloWeb.ResumeFormexController.create/2
Parameters: %{"_csrf_token" => "OgN4fAQFGwBJMG5xOxQpS38aIyFSJgAAmk7M0HNOqtA2Pdaz7Nvp3A==", "_utf8" => "тЬУ", "resume" => %{"birthday"
=> %{"day" => "2", "month" => "3", "year" => "2014"}, "data" => %{"experiences" => %{"0" => %{"city" => "Kiev", "end_date" => %{"day" => "20", "month" => "12", "year" => "2016"}, "formex_id" => "652e666a-6d9e-450c-bf5d-ee9f0d6e6b66", "position" => "Chef welder", "start_date" => %{"day" => "1", "month" => "8", "year" => "2014"}}, "1" => %{"city" => "Lvov", "end_date" => %{"day" => "1", "month" => "1", "year" => "2019"}, "formex_id" => "e89497aa-2f65-4aa5-b42e-4417569aeed7", "position" => "Master", "start_date" => %{"day" => "20", "month" => "12", "year" => "2016"}}}, "id" => "", "welder" => %{"horizontal" => "true", "id" => "", "vertical" => "false"}}, "full_name" => "John Doe", "joined_at" => %{"day" => "4", "hour" => "3", "minute" => "10", "month" => "6", "year" => "2016"}, "next_event" => %{"day" => "6", "hour" => "11", "minute" => "0", "month" => "10", "year" => "2019"}, "start_work_time" => %{"hour" => "7", "minute" => "30"}}}
Pipelines: [:browser]
iex(2)> [info] Sent 500 in 47ms
iex(2)> [error] #PID<0.1056.0> running HelloWeb.Endpoint (connection #PID<0.1055.0>, stream id 1) terminated
Server: localhost:4000 (http)
Request: POST /resumes-formex
** (exit) an exception was raised:
** (KeyError) key :_csrf_token not found in: %Hello.Hr.Resume{__meta__: #Ecto.Schema.Metadata<:built, "resumes">, birthday: nil, data: nil, full_name: nil, id: nil, inserted_at: nil, joined_at: nil, next_event: nil, start_work_time: nil, updated_at: nil}
(stdlib) :maps.get(:_csrf_token, %Hello.Hr.Resume{__meta__: #Ecto.Schema.Metadata<:built, "resumes">, birthday: nil, data: nil,
full_name: nil, id: nil, inserted_at: nil, joined_at: nil, next_event: nil, start_work_time: nil, updated_at: nil})
(elixir) lib/map.ex:267: Map.update!/3
(stdlib) maps.erl:257: :maps.fold_1/3
(formex) lib/formex/builder.ex:146: Formex.Builder.apply_params/1
(formex) lib/formex/builder.ex:52: Formex.Builder.create_form/5
(hello) lib/hello_web/controllers/resume_formex_controller.ex:18: HelloWeb.ResumeFormexController.create/2
(hello) lib/hello_web/controllers/resume_formex_controller.ex:1: HelloWeb.ResumeFormexController.action/2
(hello) lib/hello_web/controllers/resume_formex_controller.ex:1: HelloWeb.ResumeFormexController.phoenix_controller_pipeline/2
(hello) lib/hello_web/endpoint.ex:1: HelloWeb.Endpoint.instrument/4
(phoenix) lib/phoenix/router.ex:275: Phoenix.Router.__call__/1
(hello) lib/hello_web/endpoint.ex:1: HelloWeb.Endpoint.plug_builder_call/2
(hello) lib/plug/debugger.ex:122: HelloWeb.Endpoint."call (overridable 3)"/2
(hello) lib/hello_web/endpoint.ex:1: HelloWeb.Endpoint.call/2
(phoenix) lib/phoenix/endpoint/cowboy2_handler.ex:34: Phoenix.Endpoint.Cowboy2Handler.init/2
(cowboy) d:/test/hello/deps/cowboy/src/cowboy_handler.erl:41: :cowboy_handler.execute/2
(cowboy) d:/test/hello/deps/cowboy/src/cowboy_stream_h.erl:296: :cowboy_stream_h.execute/3
(cowboy) d:/test/hello/deps/cowboy/src/cowboy_stream_h.erl:274: :cowboy_stream_h.request_process/3
(stdlib) proc_lib.erl:249: :proc_lib.init_p_do_apply/3
Would you be interested in a pull request that adds mix format
formatting and credo
linting to the codebase?
It shoulds be pretty simple:
.formatter.exs
to define rules (example: https://github.com/mirego/accent/blob/master/.formatter.exs).credo.exs
to define rules (example: https://github.com/mirego/accent/blob/master/.credo.exs)By adding mix format
, contributing would be easier because you don’t have to match the style of the author. And by adding credo
it would remove dead code, warnings, unused alias, etc
Thanks for the great lib! Formex is saving us lots of time 👍
My installation is fresh new Phoenix 1.4, so there is a webpack.js project builder.
I added a dependency to the /assets/package.json file and it looks like this:
"dependencies": {
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html",
"formex": "file:../deps/formex"
}
And added a line into /assets/js/app.js according to the documentation:
import {Collection} from 'formex'
But got the error:
ERROR in ./js/app.js
Module not found: Error: Can't resolve 'formex' in 'd:\test\hello\assets\js'
Is there a way to access change set that have been defined with the schema rather than defining the in the validator section of the form type. This will just greatly simplify the process of code organisation. This just means that we will have access to the change set from other parts of the project.
I am still reviewing the formex_ecto
My config looks like this:
config :formex,
repo: App.Repo,
validator: Formex.Ecto.ChangesetValidator,
translate_error: &AppWeb.ErrorHelpers.translate_error/1
The translate error function capture is not compatible with distillery, so I can't build a release. bitwalker/distillery#185
If I don't use translate_error config option, I get BadFunctionError: expected a function, got: nil
in lib/formex/validator.ex:96
Any possible solution?
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.