diff --git a/modules/mod_survey/dispatch/survey b/modules/mod_survey/dispatch/survey index af6e4c3873..4528501b38 100644 --- a/modules/mod_survey/dispatch/survey +++ b/modules/mod_survey/dispatch/survey @@ -1,4 +1,7 @@ [ - {survey, ["survey", id], resource_page, [ {template, "survey.tpl"} ]}, - {survey, ["survey", id, slug], resource_page, [ {template, "survey.tpl"} ]} + {survey_results, ["survey", "results", id], resource_page, [ {template, "survey_results.tpl"} ]}, + {survey_results, ["survey", "results", id, slug], resource_page, [ {template, "survey_results.tpl"} ]}, + + {survey, ["survey", id], resource_page, [ {template, "survey.tpl"} ]}, + {survey, ["survey", id, slug], resource_page, [ {template, "survey.tpl"} ]} ]. diff --git a/modules/mod_survey/mod_survey.erl b/modules/mod_survey/mod_survey.erl index 0d1717c134..8d6b6746f1 100644 --- a/modules/mod_survey/mod_survey.erl +++ b/modules/mod_survey/mod_survey.erl @@ -29,7 +29,10 @@ -export([ event/2, redraw_questions/2, - delete_question/3 + delete_question/3, + + question_to_props/1, + module_name/1 ]). -include_lib("zotonic.hrl"). @@ -43,8 +46,26 @@ init(Context) -> %% @doc Handle drag/drop events from the survey admin event({sort, Items, {dragdrop, {survey, [{id,Id}]}, _Delegate, "survey"}}, Context) -> event_sort(Id, Items, Context); + event({submit, {survey_submit, [{id,SurveyId}]}, FormId, _FormId}, Context) -> - survey_submit:submit(SurveyId, FormId, Context). + survey_submit:submit(SurveyId, FormId, Context); + +event({postback, {survey_start, [{id, SurveyId}]}, _, _}, Context) -> + render_next_page(SurveyId, 1, [], Context); + +event({submit, {survey_next, Args}, _, _}, Context) -> + {id, SurveyId} = proplists:lookup(id, Args), + {page_nr, PageNr} = proplists:lookup(page_nr, Args), + {answers, Answers} = proplists:lookup(answers, Args), + render_next_page(SurveyId, PageNr+1, Answers, Context); + +event({postback, {survey_back, Args}, _, _}, Context) -> + {id, SurveyId} = proplists:lookup(id, Args), + {page_nr, PageNr} = proplists:lookup(page_nr, Args), + {answers, Answers} = proplists:lookup(answers, Args), + render_next_page(SurveyId, PageNr-1, Answers, Context). + + %%==================================================================== %% support functions @@ -139,6 +160,121 @@ new_question(Type) -> Mod:new(). +%% @doc Fetch the next page from the survey, update the page view +render_next_page(Id, 0, Answers, Context) -> + z_render:update("survey-question", #render{template="_survey_start.tpl", vars=[{id,Id},{answers,Answers}]}, Context); +render_next_page(Id, PageNr, Answers, Context) -> + As = z_context:get_q_all_noz(Context), + Answers1 = lists:foldl(fun({Arg,_Val}, Acc) -> proplists:delete(Arg, Acc) end, Answers, As), + Answers2 = Answers1 ++ As, + case m_rsc:p(Id, survey, Context) of + {survey, QuestionIds, Questions} -> + Qs = [ proplists:get_value(QId, Questions) || QId <- QuestionIds ], + Qs1 = [ Q || Q <- Qs, Q /= undefined ], + + case fetch_page(PageNr, Qs1) of + {L,NewPageNr} when is_list(L) -> + % A new list of questions, PageNr might be another than expected + Vars = [ {id, Id}, + {page_nr, NewPageNr}, + {questions, [ question_to_props(Q) || Q <- L ]}, + {pages, count_pages(Qs1)}, + {answers, Answers2}], + z_render:update("survey-question", #render{template="_survey_question_page.tpl", vars=Vars}, Context); + last -> + % That was the last page. Show a thank you and save the result. + case do_submit(Id, QuestionIds, Questions, Answers2, Context) of + ok -> + z_render:update("survey-question", #render{template="_survey_end.tpl", vars=[{id,Id}]}, Context); + {error, _Reason} -> + z_render:update("survey-question", #render{template="_survey_error.tpl", vars=[{id,Id}]}, Context) + end + end; + _NoSurvey -> + % No survey defined, show an error page. + z_render:update("survey-question", #render{template="_survey_empty.tpl", vars=[{id,Id}]}, Context) + end. + + + %% @doc Count the number of pages in the survey + count_pages([]) -> + 0; + count_pages(L) -> + count_pages(L, 1). + + count_pages([], N) -> + N; + count_pages([#survey_question{type=pagebreak}|L], N) -> + L1 = lists:dropwhile(fun(#survey_question{type=pagebreak}) -> true; (_) -> false end, L), + count_pages(L1, N+1); + count_pages([_|L], N) -> + count_pages(L, N). + + %% @doc Fetch the Nth page. Could return another page due to jumps in the pagebreaks. + fetch_page(_Nr, []) -> + last; + fetch_page(Nr, L) -> + fetch_page(1, Nr, L). + + fetch_page(_, _, []) -> + last; + fetch_page(N, Nr, L) when N >= Nr -> + L1 = lists:takewhile(fun(#survey_question{type=pagebreak}) -> false; (_) -> true end, L), + {L1, N}; + fetch_page(N, Nr, [#survey_question{type=pagebreak}|L]) when N < Nr -> + L1 = lists:dropwhile(fun(#survey_question{type=pagebreak}) -> true; (_) -> false end, L), + fetch_page(N+1, Nr, L1); + fetch_page(N, Nr, [_|L]) -> + fetch_page(N, Nr, L). + + +%% @doc Map a question to template friendly properties +question_to_props(Q) -> + [ + {name, Q#survey_question.name}, + {type, Q#survey_question.type}, + {question, Q#survey_question.question}, + {text, Q#survey_question.text}, + {parts, Q#survey_question.parts}, + {html, Q#survey_question.html}, + {is_required, Q#survey_question.is_required} + ]. + + +%% @doc Collect all answers per question, save to the database. +do_submit(SurveyId, QuestionIds, Questions, Answers, Context) -> + {FoundAnswers, Missing} = collect_answers(QuestionIds, Questions, Answers), + case Missing of + [] -> + m_survey:insert_survey_submission(SurveyId, FoundAnswers, Context), + ok; + _ -> + {error, notfound} + end. + + +%% @doc Collect all answers, report any missing answers. +%% @type collect_answers(proplist(), Context) -> {AnswerList, MissingIdsList} +collect_answers(QIds, Qs, Answers) -> + collect_answers(QIds, Qs, Answers, [], []). + + +collect_answers([], _Qs, _Answers, FoundAnswers, Missing) -> + {FoundAnswers, Missing}; +collect_answers([QId|QIds], Qs, Answers, FoundAnswers, Missing) -> + Q = proplists:get_value(QId, Qs), + Module = module_name(Q), + case Module:answer(Q, Answers) of + {ok, none} -> collect_answers(QIds, Qs, Answers, FoundAnswers, Missing); + {ok, AnswerList} -> collect_answers(QIds, Qs, Answers, [{QId, AnswerList}|FoundAnswers], Missing); + {error, missing} -> collect_answers(QIds, Qs, Answers, FoundAnswers, [QId|Missing]) + end. + +module_name(#survey_question{type=Type}) -> + list_to_atom("survey_q_"++atom_to_list(Type)). + + + datamodel() -> [ {categories, [ diff --git a/modules/mod_survey/models/m_survey.erl b/modules/mod_survey/models/m_survey.erl index 0cc58bd4a5..a283ad8f2d 100644 --- a/modules/mod_survey/models/m_survey.erl +++ b/modules/mod_survey/models/m_survey.erl @@ -47,7 +47,12 @@ m_find_value(Id, #m{value=questions}, Context) -> question_to_value(QuestionIds, Questions, []); undefined -> undefined - end. + end; +m_find_value(results, #m{value=undefined} = M, _Context) -> + M#m{value=results}; +m_find_value(Id, #m{value=results}, Context) -> + prepare_results(Id, Context). + %% @doc Transform a m_config value to a list, used for template loops %% @spec m_to_list(Source, Context) @@ -61,45 +66,79 @@ m_value(#m{value=undefined}, _Context) -> -%% @doc Transform a list of survey questions to template friendly proplists +%% @doc Transform a list of survey questions to admin template friendly proplists question_to_value([], _, Acc) -> lists:reverse(Acc); question_to_value([Id|Ids], Qs, Acc) -> Q = proplists:get_value(Id, Qs), question_to_value(Ids, Qs, [question_to_value1(Id, Q)|Acc]). - question_to_value1(Id, #survey_question{type=Type, name=Name, html=Html}) -> - {Id, [{id, Id}, {type, Type}, {name, Name}, {html, Html}]}; - question_to_value1(Id, undefined) -> - {Id, [{id, Id}, {type, undefined}]}. + question_to_value1(Id, Q) -> + {Id, [{id, Id} | mod_survey:question_to_props(Q)]}. -%% @doc Save a survey, connect to the current visitor and user (if any) +%% @doc Save a survey, connect to the current user (if any) insert_survey_submission(SurveyId, Answers, Context) -> UserId = z_acl:user(Context), %% Delete previous answers of this user, if any case UserId of - undefined -> nop; - _Other -> z_db:q("delete from survey_answer where survey_id = $1 and user_id = $2", [SurveyId, UserId], Context) + undefined -> + PersistentId = z_context:persistent_id(Context), + z_db:q("delete from survey_answer where survey_id = $1 and persistent = $2", [SurveyId, PersistentId], Context); + _Other -> + z_db:q("delete from survey_answer where survey_id = $1 and user_id = $2", [SurveyId, UserId], Context), + PersistentId = undefined end, - insert_questions(SurveyId, UserId, Answers, Context). + insert_questions(SurveyId, UserId, PersistentId, Answers, Context). - insert_questions(_SurveyId, _UserId, [], _Context) -> + insert_questions(_SurveyId, _UserId, _PersistentId, [], _Context) -> ok; - insert_questions(SurveyId, UserId, [{QuestionId, Answers}|Rest], Context) -> - insert_answers(SurveyId, UserId, QuestionId, Answers, Context), - insert_questions(SurveyId, UserId, Rest, Context). + insert_questions(SurveyId, UserId, PersistentId, [{QuestionId, Answers}|Rest], Context) -> + insert_answers(SurveyId, UserId, PersistentId, QuestionId, Answers, Context), + insert_questions(SurveyId, UserId, PersistentId, Rest, Context). - insert_answers(_SurveyId, _UserId, _QuestionId, [], _Context) -> + insert_answers(_SurveyId, _UserId, _PersistentId, _QuestionId, [], _Context) -> ok; - insert_answers(SurveyId, UserId, QuestionId, [{Name, Answer}|As], Context) -> + insert_answers(SurveyId, UserId, PersistentId, QuestionId, [{Name, Answer}|As], Context) -> Args = case Answer of - {text, Text} -> [SurveyId, UserId, QuestionId, Name, undefined, Text]; - Value -> [SurveyId, UserId, QuestionId, Name, z_convert:to_list(Value), undefined] + {text, Text} -> [SurveyId, UserId, PersistentId, QuestionId, Name, undefined, Text]; + Value -> [SurveyId, UserId, PersistentId, QuestionId, Name, z_convert:to_list(Value), undefined] end, - z_db:q("insert into survey_answer (survey_id, user_id, question, name, value, text) values ($1, $2, $3, $4, $5, $6)", Args, Context), - insert_answers(SurveyId, UserId, QuestionId, As, Context). + z_db:q("insert into survey_answer (survey_id, user_id, persistent, question, name, value, text) + values ($1, $2, $3, $4, $5, $6, $7)", + Args, + Context), + insert_answers(SurveyId, UserId, PersistentId, QuestionId, As, Context). + + +prepare_results(SurveyId, Context) -> + case m_rsc:p(SurveyId, survey, Context) of + {survey, QuestionIds, Questions} -> + Stats = survey_stats(SurveyId, Context), + [ + prepare_result(proplists:get_value(QId, Questions), + proplists:get_value(z_convert:to_binary(QId), Stats)) + || QId <- QuestionIds + ]; + undefined -> + undefined + end. + + prepare_result(Question, Stats) -> + [ + Stats, + prep_chart(Question, Stats), + mod_survey:question_to_props(Question) + ]. + + +prep_chart(_Q, undefined) -> + undefined; +prep_chart(Q, Stats) -> + M = mod_survey:module_name(Q), + M:prep_chart(Q, Stats). + %% @doc Fetch the aggregate answers of a survey, omitting the open text answers. @@ -137,6 +176,7 @@ install(Context) -> #column_def{name=id, type="serial", is_nullable=false}, #column_def{name=survey_id, type="integer", is_nullable=false}, #column_def{name=user_id, type="integer", is_nullable=true}, + #column_def{name=persistent, type="character varying", length=32, is_nullable=true}, #column_def{name=question, type="character varying", length=32, is_nullable=false}, #column_def{name=name, type="character varying", length=32, is_nullable=false}, #column_def{name=value, type="character varying", length=80, is_nullable=true}, @@ -153,11 +193,12 @@ install(Context) -> z_db:equery("alter table survey_answer add constraint fk_survey_answer_user_id foreign key (user_id) references rsc(id) on update cascade on delete cascade", Context), - + %% For aggregating answers to survey questions (group by name) z_db:equery("create index survey_answer_survey_name_key on survey_answer(survey_id, name)", Context), z_db:equery("create index survey_answer_survey_question_key on survey_answer(survey_id, question)", Context), z_db:equery("create index survey_answer_survey_user_key on survey_answer(survey_id, user_id)", Context), + z_db:equery("create index survey_answer_survey_persistent_key on survey_answer(survey_id, persistent)", Context), ok. diff --git a/modules/mod_survey/questions/survey_q_likert.erl b/modules/mod_survey/questions/survey_q_likert.erl index d04356f167..ab648ac07f 100644 --- a/modules/mod_survey/questions/survey_q_likert.erl +++ b/modules/mod_survey/questions/survey_q_likert.erl @@ -4,9 +4,11 @@ new/0, question_props/1, render/1, - answer/2 + answer/2, + prep_chart/2 ]). +-include("zotonic.hrl"). -include("../survey.hrl"). new() -> @@ -54,8 +56,24 @@ render(Q) -> ]) }. -answer(#survey_question{name=Name}, Context) -> - case z_context:get_q(Name, Context) of +answer(#survey_question{name=Name}, Answers) -> + case proplists:get_value(Name, Answers) of [C] when C >= $1, C =< $5 -> {ok, [{Name, C - $0}]}; undefined -> {error, missing} end. + + +prep_chart(_Q, []) -> + undefined; +prep_chart(_Q, [{_, Vals}]) -> + Labels = [<<"1">>,<<"2">>,<<"3">>,<<"4">>,<<"5">>], + LabelsDisplay = [<<"Strongly agree">>,<<"Agree">>,<<"Neutral">>,<<"Disagree">>,<<"Strongly disagree">>], + + Values = [ proplists:get_value(C, Vals, 0) || C <- Labels ], + Sum = case lists:sum(Values) of 0 -> 1; N -> N end, + Perc = [ round(V*100/Sum) || V <- Values ], + [ + {values, lists:zip(LabelsDisplay, Values)}, + {type, "pie"}, + {data, lists:zip(LabelsDisplay, Perc)} + ]. diff --git a/modules/mod_survey/questions/survey_q_longanswer.erl b/modules/mod_survey/questions/survey_q_longanswer.erl index 5721cedfec..dacabf6f42 100644 --- a/modules/mod_survey/questions/survey_q_longanswer.erl +++ b/modules/mod_survey/questions/survey_q_longanswer.erl @@ -49,9 +49,9 @@ render(Q) -> ]) }. -answer(Q, Context) -> +answer(Q, Answers) -> Name = Q#survey_question.name, - case z_context:get_q(Name, Context) of + case proplists:get_value(Name, Answers) of undefined -> {error, missing}; Value -> case z_string:trim(Value) of [] -> {error, missing}; diff --git a/modules/mod_survey/questions/survey_q_narrative.erl b/modules/mod_survey/questions/survey_q_narrative.erl index 76a3045732..9a43335753 100644 --- a/modules/mod_survey/questions/survey_q_narrative.erl +++ b/modules/mod_survey/questions/survey_q_narrative.erl @@ -39,54 +39,65 @@ Use [name=first|second|third] for a drop down menu named \"name\" with the given ]. render(Q) -> - {Html, _Inputs} = parse(z_convert:to_list(Q#survey_question.text)), + {Parts, _Inputs} = parse(z_convert:to_list(Q#survey_question.text)), Q#survey_question{ name = "", text = iolist_to_binary(Q#survey_question.text), question = "", - html = iolist_to_binary(Html) + parts = Parts, + html = iolist_to_binary([build(P) || P <- Parts]) }. -answer(Q, Context) -> +answer(Q, Answers) -> {_Html, Inputs} = parse(z_convert:to_list(Q#survey_question.text)), - answer_inputs(Inputs, Context, []). + answer_inputs(Inputs, Answers, []). -answer_inputs([], _Context, Acc) -> +answer_inputs([], _Answers, Acc) -> {ok, Acc}; -answer_inputs([{IsSelect,Name}|Rest], Context, Acc) -> - case z_context:get_q(Name, Context) of +answer_inputs([{IsSelect,Name}|Rest], Answers, Acc) -> + case proplists:get_value(Name, Answers) of undefined -> {error, missing}; Value -> case z_string:trim(Value) of [] -> {error, missing}; V -> V1 = case IsSelect of true -> V; false -> {text, V} end, - answer_inputs(Rest, Context, [{Name,V1}|Acc]) + answer_inputs(Rest, Answers, [{Name,V1}|Acc]) end end. parse(Text) -> - parse(Text, in_text, [], [], []). + parse(Text, [], []). -parse([], _State, _Buff, Acc, InputAcc) -> +parse([], Acc, InputAcc) -> {lists:reverse(Acc), InputAcc}; -parse([$[|T], in_text, [], Acc, InputAcc) -> - parse(T, in_input, [], Acc, InputAcc); -parse([$]|T], in_input, Input, Acc, InputAcc) -> - Input1 = lists:reverse(Input), +parse([$[|T], Acc, InputAcc) -> + {Input1,T1} = lists:splitwith(fun(C) -> C /= $] end, T), IsSelect = is_select(Input1), - {Name, Elt} = case IsSelect of - true -> build_select(Input1); - false -> build_input(Input1) + Elt = case IsSelect of + true -> + {Name, Options} = split_select(Input1), + {select, Name, Options}; + false -> + Name = z_string:trim(Input1), + Length = length(Input1), + {input, Name, Length} end, - parse(T, in_text, [], [Elt|Acc], [{IsSelect, Name}|InputAcc]); -parse([H|T], in_input, Input, Acc, InputAcc) -> - parse(T, in_input, [H|Input], Acc, InputAcc); -parse([10|T], in_text, [], Acc, InputAcc) -> - parse(T, in_text, [], [10,"
"|Acc], InputAcc); -parse([H|T], in_text, [], Acc, InputAcc) -> - parse(T, in_text, [], [H|Acc], InputAcc). + Acc1 = [Elt|Acc], + InputAcc1 = [{IsSelect, Name}|InputAcc], + case T1 of + [] -> parse([], Acc1, InputAcc1); + [$]|T2] -> parse(T2, Acc1, InputAcc1) + end; +parse(T, Acc, InputAcc) -> + {Text,Rest} = lists:splitwith(fun(C) -> C /= $[ end, T), + Text1 = lists:map(fun(10) -> "
"; + (C) -> C + end, + z_convert:to_list(z_html:escape(Text))), + Acc1 = [{html, [], iolist_to_binary(Text1)}|Acc], + parse(Rest, Acc1, InputAcc). is_select([]) -> false; @@ -94,20 +105,20 @@ is_select([$=|_]) -> true; is_select([$||_]) -> true; is_select([_|T]) -> is_select(T). -build_select(I) -> - {Name, Options} = split_select(I), - {Name, [ + +build({input, Name, Length}) -> + [ + "" + ]; +build({select, Name, Options}) -> + [ "" - ]}. + ]; +build({html, [], Html}) -> + Html. -build_input(I) -> - Name = z_string:trim(I), - Length = length(I), - {Name, [ - "" - ]}. options([], Acc) -> lists:reverse(Acc); diff --git a/modules/mod_survey/questions/survey_q_pagebreak.erl b/modules/mod_survey/questions/survey_q_pagebreak.erl new file mode 100644 index 0000000000..bda593854e --- /dev/null +++ b/modules/mod_survey/questions/survey_q_pagebreak.erl @@ -0,0 +1,58 @@ +-module(survey_q_pagebreak). + +-export([ + new/0, + question_props/1, + render/1, + answer/2 +]). + +-include("../survey.hrl"). + +new() -> + Q = #survey_question{ + type = pagebreak, + name = z_ids:identifier(5), + text = "", + question = "" + }, + render(Q). + +question_props(Q) -> + [ + {explanation, "Enter the optional condition for next page."}, + + {has_question, true}, + {has_text, true}, + {has_name, true}, + + {question_label, "To question"}, + {text_label, "Condition"}, + + {type, Q#survey_question.type}, + {name, Q#survey_question.name}, + {question, Q#survey_question.question}, + {text, Q#survey_question.text} + ]. + +render(Q) -> + Q#survey_question{ + question = iolist_to_binary(Q#survey_question.question), + html = iolist_to_binary([ + "Pagebreak

", + case z_html:escape(Q#survey_question.question) of + <<>> -> "(no jump)"; + To -> ["", To, ""] + end, + " if ", + case z_html:escape(Q#survey_question.text) of + <<>> -> "(no condition)"; + Cond -> ["", Cond, ""] + end + ]) + }. + +answer(_Q, Context) -> + {ok, none}. + + diff --git a/modules/mod_survey/questions/survey_q_prompt.erl b/modules/mod_survey/questions/survey_q_prompt.erl index 7a4584f849..87bbe3a24c 100644 --- a/modules/mod_survey/questions/survey_q_prompt.erl +++ b/modules/mod_survey/questions/survey_q_prompt.erl @@ -43,6 +43,6 @@ render(Q) -> html = iolist_to_binary(["

", z_html:escape(Q#survey_question.question), "

"]) }. -answer(_Q, _Context) -> - {ok, []}. +answer(_Q, _Answers) -> + {ok, none}. diff --git a/modules/mod_survey/questions/survey_q_shortanswer.erl b/modules/mod_survey/questions/survey_q_shortanswer.erl index b845197d5a..1b46bd22f1 100644 --- a/modules/mod_survey/questions/survey_q_shortanswer.erl +++ b/modules/mod_survey/questions/survey_q_shortanswer.erl @@ -49,9 +49,9 @@ render(Q) -> ]) }. -answer(Q, Context) -> +answer(Q, Answers) -> Name = Q#survey_question.name, - case z_context:get_q(Name, Context) of + case proplists:get_value(Name, Answers) of undefined -> {error, missing}; Value -> case z_string:trim(Value) of [] -> {error, missing}; diff --git a/modules/mod_survey/questions/survey_q_subhead.erl b/modules/mod_survey/questions/survey_q_subhead.erl index a0f4b3c8c3..0536d3a92d 100644 --- a/modules/mod_survey/questions/survey_q_subhead.erl +++ b/modules/mod_survey/questions/survey_q_subhead.erl @@ -12,7 +12,7 @@ new() -> Q = #survey_question{ type = subhead, - name = "", + name = z_ids:identifier(5), text = "", question = <<"This is a subhead">> }, @@ -24,7 +24,7 @@ question_props(Q) -> {has_question, true}, {has_text, false}, - {has_name, false}, + {has_name, true}, {question_label, "Subhead"}, {text_label, ""}, @@ -37,13 +37,12 @@ question_props(Q) -> render(Q) -> Q#survey_question{ - name = "", text = "", question = iolist_to_binary(Q#survey_question.question), html = iolist_to_binary(["

", z_html:escape(Q#survey_question.question), "

"]) }. -answer(_Q, Context) -> - {ok, []}. +answer(_Q, _Answers) -> + {ok, none}. diff --git a/modules/mod_survey/questions/survey_q_textblock.erl b/modules/mod_survey/questions/survey_q_textblock.erl index d1f5ca66ce..0d949ad987 100644 --- a/modules/mod_survey/questions/survey_q_textblock.erl +++ b/modules/mod_survey/questions/survey_q_textblock.erl @@ -12,7 +12,7 @@ new() -> Q = #survey_question{ type= textblock, - name = "", + name = z_ids:identifier(5), text = "", question = <<"This is a paragraph-length textblock, useful for instructions. If you need more than one paragraph, simply select a textBlock for each paragraph.">> }, @@ -24,7 +24,7 @@ question_props(Q) -> {has_question, true}, {has_text, false}, - {has_name, false}, + {has_name, true}, {question_label, "Text"}, {text_label, ""}, @@ -37,12 +37,11 @@ question_props(Q) -> render(Q) -> Q#survey_question{ - name = "", text = "", question = iolist_to_binary(Q#survey_question.question), html = iolist_to_binary(["

", z_html:escape(Q#survey_question.question), "

"]) }. -answer(_Q, Context) -> - {ok, []}. +answer(_Q, _Answers) -> + {ok, none}. diff --git a/modules/mod_survey/questions/survey_q_thurstone.erl b/modules/mod_survey/questions/survey_q_thurstone.erl index 94ba16aeee..6211b45073 100644 --- a/modules/mod_survey/questions/survey_q_thurstone.erl +++ b/modules/mod_survey/questions/survey_q_thurstone.erl @@ -4,9 +4,11 @@ new/0, question_props/1, render/1, - answer/2 + answer/2, + prep_chart/2 ]). +-include("zotonic.hrl"). -include("../survey.hrl"). new() -> @@ -46,6 +48,7 @@ render(Q) -> Rs = radio(Options, 1, Name, []), Q#survey_question{ question = iolist_to_binary(Q#survey_question.question), + parts = [ z_html:escape(Opt) || Opt <- Options ], html = iolist_to_binary([ "

", z_html:escape(Q#survey_question.question), "

", "

", @@ -69,12 +72,26 @@ radio([H|T], N, Name, Acc) -> radio(T, N+1, Name, [R|Acc]). -answer(Q, Context) -> +answer(Q, Answers) -> Name = Q#survey_question.name, Options = split_options(Q#survey_question.text), - case z_context:get_q(Name, Context) of + case proplists:get_value(Name, Answers) of undefined -> {error, missing}; N -> {ok, [{Name, lists:nth(list_to_integer(N), Options)}]} end. + +prep_chart(_Q, []) -> + undefined; +prep_chart(Q, [{_, Vals}]) -> + Labels = [ z_convert:to_binary(Lab) || Lab <- split_options(Q#survey_question.text) ], + Values = [ proplists:get_value(C, Vals, 0) || C <- Labels ], + Sum = case lists:sum(Values) of 0 -> 1; N -> N end, + Perc = [ round(V*100/Sum) || V <- Values ], + [ + {values, lists:zip(Labels, Values)}, + {type, "pie"}, + {data, lists:zip(Labels, Perc)} + ]. + \ No newline at end of file diff --git a/modules/mod_survey/questions/survey_q_truefalse.erl b/modules/mod_survey/questions/survey_q_truefalse.erl index 660aea85d7..0f13a99f0e 100644 --- a/modules/mod_survey/questions/survey_q_truefalse.erl +++ b/modules/mod_survey/questions/survey_q_truefalse.erl @@ -4,7 +4,8 @@ new/0, question_props/1, render/1, - answer/2 + answer/2, + prep_chart/2 ]). -include("../survey.hrl"). @@ -49,10 +50,25 @@ render(Q) -> ]) }. -answer(#survey_question{name=Name}, Context) -> - case z_context:get_q(Name, Context) of - "true" -> {ok, [{Name, true}]}; - "false" -> {ok, [{Name, false}]}; +answer(#survey_question{name=Name}, Answers) -> + case proplists:get_value(Name, Answers) of + "1" -> {ok, [{Name, true}]}; + "0" -> {ok, [{Name, false}]}; undefined -> {error, missing} end. + + +prep_chart(_Q, []) -> + undefined; +prep_chart(_Q, [{_, Vals}]) -> + True = proplists:get_value(<<"true">>, Vals, 0), + False = proplists:get_value(<<"false">>, Vals, 0), + Total = True + False, + TrueP = round(True * 100 / Total), + FalseP = 100 - TrueP, + [ + {values, [{"true", True}, {"false", False}]}, + {type, "pie"}, + {data, [["true", TrueP], ["false", FalseP]]} + ]. diff --git a/modules/mod_survey/questions/survey_q_yesno.erl b/modules/mod_survey/questions/survey_q_yesno.erl index e3bf689aa8..e8c7002b49 100644 --- a/modules/mod_survey/questions/survey_q_yesno.erl +++ b/modules/mod_survey/questions/survey_q_yesno.erl @@ -4,7 +4,8 @@ new/0, question_props/1, render/1, - answer/2 + answer/2, + prep_chart/2 ]). -include("../survey.hrl"). @@ -50,9 +51,24 @@ render(Q) -> ]) }. -answer(#survey_question{name=Name}, Context) -> - case z_context:get_q(Name, Context) of - "yes" -> {ok, [{Name, yes}]}; - "no" -> {ok, [{Name, no}]}; +answer(#survey_question{name=Name}, Answers) -> + case proplists:get_value(Name, Answers) of + "1" -> {ok, [{Name, yes}]}; + "0" -> {ok, [{Name, no}]}; undefined -> {error, missing} end. + + +prep_chart(_Q, []) -> + undefined; +prep_chart(_Q, [{_, Vals}]) -> + Yes = proplists:get_value(<<"yes">>, Vals, 0), + No = proplists:get_value(<<"no">>, Vals, 0), + Total = Yes + No, + YesP = round(Yes * 100 / Total), + NoP = 100 - YesP, + [ + {values, [{"yes", Yes}, {"no", No}]}, + {type, "pie"}, + {data, [["yes", YesP], ["no", NoP]]} + ]. diff --git a/modules/mod_survey/survey.hrl b/modules/mod_survey/survey.hrl index 2d8ab232fe..162982e27a 100644 --- a/modules/mod_survey/survey.hrl +++ b/modules/mod_survey/survey.hrl @@ -19,4 +19,4 @@ %% @doc A question for in a survey --record(survey_question, {type, name, question, text, html}). +-record(survey_question, {type, name, question, text, html, parts=[], is_required=true}). diff --git a/modules/mod_survey/templates/_admin_edit_content.survey.tpl b/modules/mod_survey/templates/_admin_edit_content.survey.tpl index 57c9d3f8da..8c7b485936 100644 --- a/modules/mod_survey/templates/_admin_edit_content.survey.tpl +++ b/modules/mod_survey/templates/_admin_edit_content.survey.tpl @@ -43,7 +43,7 @@

{_ Question Templates _}

-
@@ -51,7 +51,7 @@

{_ Your survey _}

{% sorter id="survey" tag={survey id=id} group="survey" delegate="mod_survey" %} -
diff --git a/modules/mod_survey/templates/_admin_survey_questions.tpl b/modules/mod_survey/templates/_admin_survey_questions.tpl index 0b595ecd84..07ef471f3f 100644 --- a/modules/mod_survey/templates/_admin_survey_questions.tpl +++ b/modules/mod_survey/templates/_admin_survey_questions.tpl @@ -1,3 +1,9 @@ +
  • + {% survey_example type="pagebreak" %} +
  • +{% draggable id="ci1" tag={q type="pagebreak"} to_sorter="survey" %} + +
  • {% survey_example type="textblock" %}
  • diff --git a/modules/mod_survey/templates/_admin_survey_questions_edit.tpl b/modules/mod_survey/templates/_admin_survey_questions_edit.tpl index 234c35e425..95a6069d6e 100644 --- a/modules/mod_survey/templates/_admin_survey_questions_edit.tpl +++ b/modules/mod_survey/templates/_admin_survey_questions_edit.tpl @@ -1,9 +1,9 @@ -{% for q_id, q in m.survey.questions[id] %} +{% for q_id, question in m.survey.questions[id] %}
  • {{ q.name|escape }}  

    - {{ q.html|replace:['class="', 'class="nosubmit '] }} + {{ question.html|replace:['class="', 'class="nosubmit '] }}
  • {% sortable id=q_id tag=q_id%} {% wire id=#x.q_id delegate="mod_survey" action={dialog_survey_question_delete id=id question_id=q_id} %} diff --git a/modules/mod_survey/templates/_survey_end.tpl b/modules/mod_survey/templates/_survey_end.tpl new file mode 100644 index 0000000000..242a9172f3 --- /dev/null +++ b/modules/mod_survey/templates/_survey_end.tpl @@ -0,0 +1,3 @@ +

    {_ Thank you _}

    + +

    {_ Thank you for filling in our survey. Be sure to come back for other surveys! _}

    \ No newline at end of file diff --git a/modules/mod_survey/templates/_survey_error.tpl b/modules/mod_survey/templates/_survey_error.tpl new file mode 100644 index 0000000000..429c84af55 --- /dev/null +++ b/modules/mod_survey/templates/_survey_error.tpl @@ -0,0 +1,3 @@ +

    {_ Something went wrong _}

    + +

    {_ There was an error while handling your answers. We are terribly sorry about this. _}

    \ No newline at end of file diff --git a/modules/mod_survey/templates/_survey_question_likert.tpl b/modules/mod_survey/templates/_survey_question_likert.tpl new file mode 100644 index 0000000000..94c857f77c --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_likert.tpl @@ -0,0 +1,14 @@ +{% with answers[name] as ans %} +

    {{ question.question|escape }}

    + +{% endwith %} +{% validate id=#q1 name=name type={presence} %} + diff --git a/modules/mod_survey/templates/_survey_question_longanswer.tpl b/modules/mod_survey/templates/_survey_question_longanswer.tpl new file mode 100644 index 0000000000..708210c01a --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_longanswer.tpl @@ -0,0 +1,4 @@ +

    {{ question.question|escape }}

    + + +{% validate id=#long name=question.name type={presence} %} diff --git a/modules/mod_survey/templates/_survey_question_narrative.tpl b/modules/mod_survey/templates/_survey_question_narrative.tpl new file mode 100644 index 0000000000..f53e408a70 --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_narrative.tpl @@ -0,0 +1,21 @@ +

    + {% for type,name,value in question.parts %} + {% with forloop.counter, answers[name] as index, ans %} + {% if type == "html" %} + {{ value }} + {% endif %} + {% if type == "input" %} + + {% validate id=#inp.index name=name type={presence} %} + {% endif %} + {% if type == "select" %} + + {% validate id=#sel.index name=name type={presence} %} + {% endif %} + {% endwith %} + {% endfor %} +

    \ No newline at end of file diff --git a/modules/mod_survey/templates/_survey_question_page.tpl b/modules/mod_survey/templates/_survey_question_page.tpl new file mode 100644 index 0000000000..abd6146ca3 --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_page.tpl @@ -0,0 +1,52 @@ +

    {_ Question _} {{ page_nr }}/{{ pages }}

    + +{% wire id=#q type="submit" postback={survey_next id=id page_nr=page_nr answers=answers} delegate="mod_survey" %} +
    + {% for question in questions %} + {% with question.type|make_list as t %} +
    + {% if t == "subhead" %} + {% include "_survey_question_subhead.tpl" question=question name=question.name %} + {% endif %} + {% if t == "textblock" %} + {% include "_survey_question_textblock.tpl" question=question name=question.name %} + {% endif %} + {% if t == "prompt" %} + {% include "_survey_question_prompt.tpl" question=question name=question.name %} + {% endif %} + {% if t == "yesno" %} + {% include "_survey_question_yesno.tpl" question=question name=question.name %} + {% endif %} + {% if t == "truefalse" %} + {% include "_survey_question_truefalse.tpl" question=question name=question.name %} + {% endif %} + {% if t == "likert" %} + {% include "_survey_question_likert.tpl" question=question name=question.name %} + {% endif %} + {% if t == "thurstone" %} + {% include "_survey_question_thurstone.tpl" question=question name=question.name %} + {% endif %} + {% if t == "shortanswer" %} + {% include "_survey_question_shortanswer.tpl" question=question name=question.name %} + {% endif %} + {% if t == "longanswer" %} + {% include "_survey_question_longanswer.tpl" question=question name=question.name %} + {% endif %} + {% if t == "narrative" %} + {% include "_survey_question_narrative.tpl" question=question name=question.name %} + {% endif %} +
    + {% endwith %} + {% endfor %} + +
    + {% if page_nr > 1 %} + < {_ Back _} + {% else %} + < {_ Cancel _} + {% endif %} + {% wire id=#back postback={survey_back id=id page_nr=page_nr answers=answers} delegate="mod_survey" %} + +
    +
    + diff --git a/modules/mod_survey/templates/_survey_question_prompt.tpl b/modules/mod_survey/templates/_survey_question_prompt.tpl new file mode 100644 index 0000000000..8884ba1383 --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_prompt.tpl @@ -0,0 +1 @@ +

    {{ question.question|escape }}

    diff --git a/modules/mod_survey/templates/_survey_question_shortanswer.tpl b/modules/mod_survey/templates/_survey_question_shortanswer.tpl new file mode 100644 index 0000000000..741a72902d --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_shortanswer.tpl @@ -0,0 +1,4 @@ +

    {{ question.question|escape }}

    + + +{% validate id=#short name=question.name type={presence} %} diff --git a/modules/mod_survey/templates/_survey_question_subhead.tpl b/modules/mod_survey/templates/_survey_question_subhead.tpl new file mode 100644 index 0000000000..531d0a95b2 --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_subhead.tpl @@ -0,0 +1 @@ +

    {{ question.question|escape }}

    \ No newline at end of file diff --git a/modules/mod_survey/templates/_survey_question_textblock.tpl b/modules/mod_survey/templates/_survey_question_textblock.tpl new file mode 100644 index 0000000000..392af42b75 --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_textblock.tpl @@ -0,0 +1 @@ +

    {{ question.question|escape }}

    diff --git a/modules/mod_survey/templates/_survey_question_thurstone.tpl b/modules/mod_survey/templates/_survey_question_thurstone.tpl new file mode 100644 index 0000000000..87dc3d3acf --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_thurstone.tpl @@ -0,0 +1,12 @@ +{% with answers[name] as ans %} + +{% endwith %} diff --git a/modules/mod_survey/templates/_survey_question_truefalse.tpl b/modules/mod_survey/templates/_survey_question_truefalse.tpl new file mode 100644 index 0000000000..90867115e0 --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_truefalse.tpl @@ -0,0 +1,6 @@ +

    {{ question.question|escape }}

    + +{% validate id=#yes name=name type={presence} %} diff --git a/modules/mod_survey/templates/_survey_question_yesno.tpl b/modules/mod_survey/templates/_survey_question_yesno.tpl new file mode 100644 index 0000000000..ea92bb7d67 --- /dev/null +++ b/modules/mod_survey/templates/_survey_question_yesno.tpl @@ -0,0 +1,6 @@ +

    {{ question.question|escape }}

    + +{% validate id=#yes name=name type={presence} %} diff --git a/modules/mod_survey/templates/_survey_results.tpl b/modules/mod_survey/templates/_survey_results.tpl new file mode 100644 index 0000000000..b8de6270c2 --- /dev/null +++ b/modules/mod_survey/templates/_survey_results.tpl @@ -0,0 +1,25 @@ +{% for result, chart, question in m.survey.results[id] %} +
    + {% if not result %} + {% if question.type == 'subhead' %}{% include "_survey_question_subhead.tpl" %}{% endif %} + {% if question.type == 'prompt' %}{% include "_survey_question_prompt.tpl" %}{% endif %} + {% if question.type == 'texblock' %}{% include "_survey_question_textblock.tpl" %}{% endif %} + {% if question.type == 'pagebreak' %}
    {% endif %} + {% else %} +
    + {% if chart.type == "pie" %} +

    {{ question.question }}

    + {% chart_pie3d height=100 width=400 data=chart.data %} + +
    + + {% for label,value in chart.values %} + + {% endfor %} +
    {{ label }}{{ value }}
    +
    + {% endif %} +
    + {% endif %} +
    +{% endfor %} diff --git a/modules/mod_survey/templates/_survey_start.tpl b/modules/mod_survey/templates/_survey_start.tpl new file mode 100644 index 0000000000..70d617baec --- /dev/null +++ b/modules/mod_survey/templates/_survey_start.tpl @@ -0,0 +1,22 @@ +{% media m.rsc[id].media[1] width=400 height=400 class="main-image" %} + +

    + {{ m.rsc[id].summary }} +

    + +{{ m.rsc[id].body|show_media }} + +
    + + {% wire id=#survey_next + postback={survey_start id=id} + delegate="mod_survey" + %} +
    + +
    + + {% wire id=#survey_result + action={redirect dispatch="survey_results" id=id} + %} +
    diff --git a/modules/mod_survey/templates/survey.tpl b/modules/mod_survey/templates/survey.tpl index 2fd4262a51..4c5c6a5dcb 100644 --- a/modules/mod_survey/templates/survey.tpl +++ b/modules/mod_survey/templates/survey.tpl @@ -17,25 +17,8 @@ } -{% wire id=#survey type="submit" postback={survey_submit id=id} delegate="mod_survey" %} -
    -

    - {{ m.rsc[id].summary }} -

    - - {% for q_id, q in m.survey.questions[id] %} -
    - {{ q.html }} -
    - {% empty %} -

    {_ Sorry, this survey doesn't have any questions. _}

    - {% endfor %} - - -
    - -