2 * Copyright 2013 Samsung Electronics Co., Ltd
4 * Licensed under the Flora License, Version 1.1 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://floralicense.org/license/
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 /*jslint devel: true*/
18 /*global tizen, document, $, jQuery, app, UiPanel, UiContact, TemplateManager, window, Helpers */
23 function Ui(contacts) {
28 (function () { // strict mode wrapper
40 * Default tizen address book
43 addressBook: tizen.contact.getDefaultAddressBook(),
49 photoURIdefault: null,
52 * Cached contacts from address book
58 * identifiers of currently checked checkboxes
64 * @type {TemplateManager}
66 templateManager: null,
71 init: function Ui_init() {
73 this.templateManager = new TemplateManager();
74 this.helpers = new Helpers();
75 $(document).ready(this.domInit.bind(this));
76 $.mobile.tizen.disableSelection(document);
80 * When DOM is ready, initialise it (bind events)
82 domInit: function Ui_domInit() {
83 this.templateManager.loadToCache(['callView',
89 'dateRow'], this.initPages.bind(this));
93 * UI pages initializer
95 initPages: function Ui_initPages() {
96 var pages = [], body = $('body');
99 .append(this.templateManager.get('messageWindow'))
100 .append(this.templateManager.get('errorWindow'))
104 $(this.templateManager.get('callView')).children()
106 .trigger('pagecreate')
107 .trigger('pageshow');
108 pages.push(this.templateManager.get('callerHistory'));
109 body.append(pages.join(''));
110 this.removeSearchBarToHeader();
113 app.showCallHistory();
115 this.photoURIdefault = $("#header .photo").css('background-image');
121 addEvents: function Ui_addEvents() {
124 // recalculating page size when window resize
125 // (eg. when soft keybord appear)
126 $(window).on('resize', function () {
127 $.mobile.activePage.page('refresh');
130 // creating call history view
131 $('#callView').on('pagebeforeshow', function () {
132 app.showCallHistory();
135 // creating single contact history view
136 $('#historyForCallerView').on('pagebeforeshow', function () {
137 self.hideCheckboxes();
138 // move scrollbar to top
139 $('#historyForCallerView')
140 .find('.ui-content.ui-scrollview-clip')
141 .find('.ui-scrollview-view')
142 .css('-webkit-transform', 'translate3d(0px, 0px, 0px)');
143 $('#selectAllDetails').on('change', function () {
149 // HACK not dissapearing scrollview indicator
150 $('#historyForCallerView').on('pageshow', function () {
151 $('#content').css('top', '160px');
152 $('#header').css('height', '160px');
153 $('#callerListContainer')
155 '.ui-overflow-indicator-top',
156 '.ui-overflow-indicator-bottom'
160 // HACK not dissapearing scrollview indicator
161 $('#callerListContainer').on('scrollstart', function () {
164 '.ui-overflow-indicator-top',
165 '.ui-overflow-indicator-bottom'
169 // HACK not dissapearing scrollview indicator
170 $('#callerListContainer').on('scrollstop', function () {
173 '.ui-overflow-indicator-top',
174 '.ui-overflow-indicator-bottom'
178 // selecting all checkboxes
179 $('.selectAllBox').children().on('click', function () {
180 $('.selectAllBox input[type=checkbox]').trigger('click');
182 self.selectAllDetailsEach();
185 $('#calllogList').on('click', '.date', function (event) {
186 event.stopPropagation();
189 // when clicked on a log display all history from that contact
190 $('#calllogList').on(
193 function onCalllogEntryClick(event) {
194 if (event.type === 'keypress' && event.keyCode !== 13) {
197 var remoteParty = $(this)
201 app.showHistoryForCaller(remoteParty);
202 $.mobile.changePage('#historyForCallerView');
206 // run sms service on click
207 $('#smsActionBtn').on('click', function (event) {
208 event.stopPropagation();
209 event.preventDefault();
210 self.lockButtons('#smsActionBtn, #callActionBtn');
211 self.hideCheckboxes();
212 self.lockButtons('#deleteActionBtn');
213 app.sendSms($('#forCallerList').data('remoteParty'));
216 // run call service on btn click
217 $('#callActionBtn').on("click", function (event) {
218 self.lockButtons('#callActionBtn, #smsActionBtn');
219 self.hideCheckboxes();
220 self.lockButtons('#deleteActionBtn');
221 app.makeCall($('#forCallerList').data('remoteParty'));
224 $('#deleteActionBtn').on('click', function () {
225 self.changeDetailsToRemoveState();
228 // selecting all checkboxes
229 $('.selectAllBox').on('click', 'li', function () {
230 var checkbox = $(this).find(':checkbox');
231 if (self.removeMode === true) {
232 if (checkbox.attr('checked')) {
233 checkbox.attr('checked', false)
234 .data('checkboxradio')
237 checkbox.attr('checked', true)
238 .data('checkboxradio')
241 self.setSelectAllDetails();
248 '#popupCancelActionBtn',
249 this.closePopup.bind(this)
252 '#popupSubmitActionBtn',
253 this.deleteCheckedLogs.bind(this)
262 $('#errorPopup').bind(
264 popupafterclose: function () {
265 self.unlockButtons();
270 $(window).keyup(function (e) {
271 if (e.which === 13) {
272 $('input:focus').blur();
276 // bind hardware back button
277 window.addEventListener('tizenhwkey', function (e) {
278 if (e.keyName === "back") {
279 // if there is any popup close it
280 if ($.mobile.popup.active) {
281 $.mobile.popup.active.close();
282 // if app is in remove mode close it
283 } else if (self.removeMode === true) {
284 app.ui.changeDetailsToRemoveState(undefined, true);
285 // if app is on calls page close app
286 } else if ($.mobile.activePage.attr('id') === 'callView') {
287 tizen.application.getCurrentApplication().exit();
288 // else back to calls page
294 // bind callback to visbility change event
295 self.onVisibilityChange();
301 addEventsForCallerListCheckboxes:
302 function Ui_addEventsForCallerListCheckboxes() {
304 $('#forCallerList :checkbox').on('change', function (event) {
305 if ($(this).attr('checked')) {
306 $(this).attr('checked', true);
308 $(this).attr('checked', false);
310 self.setSelectAllDetails();
315 * Callback function for click on select all element
317 selectAll: function () {
318 // if select all checkbox is checked
319 if ($('#selectAllDetails').attr('checked')) {
320 this.selectCheckbox($('#selectAllDetails'), true);
322 this.selectCheckbox($('#selectAllDetails'), false);
324 // select each checkbox separatly
325 this.selectAllDetailsEach();
329 * Select single checkbox and enable delete button
331 selectCheckbox: function (obj, state) {
332 var deleteButton = $('#deleteActionBtn'), numChecked;
333 // check checkbox and refresh its view
334 obj.attr('checked', state)
335 .data('checkboxradio')
338 // check if there is any checked checkbox
339 // and toggle delete button availability
340 numChecked = $('#forCallerList input:checked').length;
341 if (this.removeMode && numChecked === 0) {
343 .addClass('ui-disabled')
344 .attr('tabIndex', '-1')
346 } else if (deleteButton.hasClass('ui-disabled')) {
348 .removeClass('ui-disabled')
349 .attr('tabIndex', '0');
354 * Returns number of selected call logs
355 * @return {number} length
357 getCountSelectedLogEntries: function Ui_getCountSelectedLogEntries() {
358 return $('#forCallerList li .toRemove label.ui-checkbox-on').length;
362 * Select each checkbox separatly
364 selectAllDetailsEach: function Ui_selectAllDetailsEach() {
366 $('#forCallerList').find('input').each(function () {
367 if ($('#selectAllDetails').attr('checked')) {
368 self.selectCheckbox($(this), true);
370 self.selectCheckbox($(this), false);
378 hideCheckboxes: function Ui_hideCheckboxes() {
380 this.selectCheckbox($('#selectAllDetails'), false);
382 $('#forCallerList').find('input').each(function () {
383 self.selectCheckbox($(this), false);
385 this.changeDetailsToRemoveState('hide');
389 * Returns css classes for specified entry
391 * @param {CallHistoryEntry} entry
394 cssClassesForEntry: function Ui_cssClassesForEntry(entry) {
396 entry.direction.toLowerCase() +
398 entry.type.replace('.', '-').toLowerCase();
402 * Check if all details checkboxes are selected
403 * and if so check selectAll element
405 setSelectAllDetails: function Ui_setSelectAllDetails() {
407 $('#forCallerList input[type="checkbox"]').length ===
408 $('#forCallerList input[checked="checked"]').length
410 this.selectCheckbox($('#selectAllDetails'), true);
412 this.selectCheckbox($('#selectAllDetails'), false);
417 * Shows popup with specified message
418 * @param {string} message
420 showPopup: function Ui_showPopup(message) {
421 $('#popupMessage').html(message);
422 $('#popup').popup('open', {'positionTo': 'window'});
428 closePopup: function Ui_closePopup() {
429 $('#popup').popup('close');
433 * Display error popup
435 showErrorPopup: function Ui_showErrorPopup(message) {
436 $('#errorPopupMessage').html(message);
437 $('#errorPopup').popup('open', {'positionTo': 'window'});
443 closeErrorPopup: function Ui_closeErrorPopup() {
444 $('#errorPopup').popup('close');
448 * Deletes checked log entries
450 deleteCheckedLogs: function Ui_deleteCheckedLogs(e) {
451 // if there is popup open close it
454 // uncheck SelectAll checkbox
455 this.selectCheckbox($('#selectAllDetails'), false);
457 // iterate through all entries
458 $('#forCallerList li.call').each(function () {
459 // if entry contains selected checkbox
460 if ($(this).find('form label').hasClass('ui-checkbox-on')) {
462 app.deleteLog($(this).data('entries')[0]);
463 // and remove connected html element
468 // if there are still some entries
469 if ($('#forCallerList li.call').length > 0) {
470 // update header info
471 this.updateCallerHeaderNumberOfEntries(
472 $('#forCallerList li.call').length
475 // if no entries change page
477 $('.ui-listview-filter .ui-input-text').val('');
478 $('.ui-listview-filter .ui-input-text').trigger('change');
479 $.mobile.changePage('#callView');
482 this.changeDetailsToRemoveState(true);
483 this.scrollToBottom();
487 * TODO Dont really know what that method exacly doing
491 changeDetailsToRemoveState: function Ui_changeDetailsToRemoveState(
495 var counter = this.getCountSelectedLogEntries(),
496 numChecked = $('#forCallerList input:checked').length,
499 if (clear === true) {
501 // uncheck all checkboxes
502 $('#forCallerList').find(':checkbox').attr('checked', false)
503 .data('checkboxradio').refresh();
504 $('.selectAllBox').find(':checkbox').attr('checked', false)
505 .data('checkboxradio').refresh();
507 // TODO it is hard ot understand this
508 if (set !== undefined) {
509 this.removeMode = false;
510 } else if (counter === 0) {
511 this.removeMode = !this.removeMode;
514 // if app is in remove mode and there are no selected checkboxes
515 if (this.removeMode && numChecked === 0) {
516 // disable delete button
517 $('#deleteActionBtn')
518 .addClass('ui-disabled')
519 .attr('tabIndex', '-1')
521 // if app is not in removeMode
522 } else if (!this.removeMode) {
523 // set delete button to enabled
524 $('#deleteActionBtn')
525 .removeClass('ui-disabled')
526 .attr('tabIndex', '0');
527 this.selectAllDetailsEach();
530 // if there are no selected entries
532 // again checking removeMode
533 if (this.removeMode) {
534 // show buttons from remove mode
535 $('#historyForCallerView .toRemove').removeClass('hidden');
536 $('#historyForCallerView .selectAllBox')
537 .removeClass('hidden');
539 $('#historyForCallerView .toRemove').addClass('hidden');
540 $('#historyForCallerView .selectAllBox').addClass('hidden');
543 // TODO I dont understand
546 'Are you sure you want to delete selected logs?' :
547 'Are you sure you want to delete selected log?'
551 // TODO need refactorin
552 // HACK probably scrollview hack
554 $('#historyForCallerView .ui-scrollview-view')
556 pos = matrix.substr(7, matrix.length - 8).split(',')[5];
557 if (pos !== undefined) {
558 $('#callerListContainer')
559 .scrollview('scrollTo', 100, parseInt(pos, 10), 10);
561 this.refreshScrollView();
566 * Renders call history list
568 * @param {CallHistoryEntry[]} callEntries
570 showCallHistory: function Ui_showCallHistory(callEntries) {
572 pdate = null, // previous date
574 elements = [], // list elements
575 len = callEntries.length, // entries length
576 tempLength = 0, // length of temporary table;
579 current, // current entry object
580 today = this.helpers.getShortDate(new Date()),
586 calllogList = $('#calllogList'),
587 calllogListContent = $('#calllogListContent'),
588 calllogListContentPos;
590 // return duplicated entries
591 function filterForSameEntry(element) {
592 return self.getNumber(current) === self.getNumber(element)
593 && current.direction === element.direction;
596 $('.selectedCount').hide();
598 for (i = 0; i < len; i = i + 1) {
599 current = callEntries[i];
600 date = this.helpers.toNativeDate(current.startTime);
602 // if date is changed create new deyLog;
603 if (date !== pdate) {
607 dayLog.counters = [];
608 groupsOfDays.push(dayLog);
612 // group entries by remote Party;
613 filterResult = dayLog.entries.filter(filterForSameEntry);
614 if (filterResult.length) {
615 index = dayLog.entries.indexOf(filterResult[0]);
616 dayLog.counters[index] += 1;
618 dayLog.entries.push(current);
619 dayLog.counters[dayLog.entries.length - 1] = 1;
622 // Create UL list with dividers;
623 len = groupsOfDays.length;
624 for (i = 0; i < len; i += 1) {
625 dayLog = groupsOfDays[i];
626 tempLength = dayLog.entries.length;
627 entryShortDate = this.helpers.getShortDate(
628 dayLog.entries[0].startTime
630 for (j = 0; j < tempLength; j = j + 1) {
639 calllogListContentPos = this.helpers.getScrollPosition(
642 calllogList.empty().append(elements);
644 //workaround solution for searching phrase remain
646 if ($("[data-type='search']").val().length !== "") {
647 calllogList.listview('refresh');
648 $("[data-type='search']").trigger("keyup");
650 .removeClass("ui-li ui-li-divider ui-bar-s")
653 calllogList.listview({
656 autodividersSelector: function (li) {
657 return $(li).find('.callDate').text() ===
658 app.ui.helpers.toNativeDate(new Date()) ?
659 "Today" : $(li).find('.callDate').text();
661 }).listview('refresh');
662 $(".ui-li-divider").removeClass().addClass("date");
666 this.helpers.scrollTo.bind(
669 calllogListContentPos
676 * @param: {CallHistoryEntry} entry
678 getNumber: function (entry) {
679 return entry.remoteParties[0].remoteParty;
683 * Returns HTML for single log entry
685 * @param {CallHistoryEntry} entry
686 * @param {number} counter
687 * @returns {HTMLPartial}
689 getCallItemRow: function Ui_getCallItemRow(entry, counter) {
690 var party = entry.remoteParties[0],
691 name = this.getNameByNumber(party.remoteParty),
695 name += ' (' + counter + ')';
698 // prepare html string
699 tpl = this.templateManager.get('callItemRow', {
701 'callTime': this.helpers.toNativeTime(entry.startTime),
702 'callDate': this.helpers.toNativeDate(entry.startTime),
703 'cssClasses': this.cssClassesForEntry(entry),
707 // return clean DOM element so that array of those could be appended
710 .data('remoteParty', entry.remoteParties[0].remoteParty)
711 .data('entries', [entry])
716 * Return readable number identifier
718 * @param {string} number
721 getNameByNumber: function (number) {
722 var i, j, contact, name;
723 // iterate through loaded contacts
724 for (i in this.contactsLoaded) {
725 if (this.contactsLoaded.hasOwnProperty(i)) {
726 contact = this.contactsLoaded[i];
727 // iterate through numbers of contact
728 for (j in contact.phoneNumbers) {
729 if (contact.phoneNumbers.hasOwnProperty(j)) {
730 // check if first 9 characters match number
731 if (contact.phoneNumbers[j].number.substr(-9)
732 === number.substr(-9)) {
734 name = contact.name.displayName;
741 return name || number || 'Unknown';
745 * Returns HTML for single caller log entry
747 * @param {CallHistoryEntry} entry
748 * @returns {HTMLElement}
750 getCallerCallLogRow: function Ui_getCallerCallLogRow(entry) {
751 return $(this.templateManager.get('callerCallItemRow', {
752 'cssClass': this.cssClassesForEntry(entry),
753 'callTime': this.helpers.toNativeTime(entry.startTime),
754 'callDuration': this.helpers.secondsToHours(entry.duration),
756 })).data('entries', [entry]).get(0);
760 * Renders call log list for specified caller
761 * TODO that methods is too long!
763 * @param {string} remoteParty
764 * @param {CallHistoryEntry[]} entries
766 showHistoryForCaller: function Ui_showHistoryForCaller(
770 var pdate = '', // previous data
773 len = entries.length, // number of entries
778 this.updateCallerHeader(entries[0], entries.length);
780 // if last call log has been removed
781 this.removedLastLog();
782 this.app.lastViewedCaller = 0;
787 .data('remoteParty', remoteParty)
788 .data('modified', false)
791 // group caller log entries by date
792 for (i = 0; i < len; i = i + 1) {
793 date = this.helpers.toNativeDate(entries[i].startTime);
795 // if date is changed render new header
796 if (date !== pdate) {
798 $(this.templateManager.get(
805 elements.push(this.getCallerCallLogRow(entries[i]));
812 // set state of delete button
813 if (elements.length > 0) {
814 $('li#delete > a').addClass('ui-btn-active');
816 $('li#delete > a').removeClass('ui-btn-active');
819 $('#forCallerList').trigger('create');
821 // change to remove mode if it was active before registering call
822 if (this.removeMode) {
823 this.removeMode = !this.removeMode;
824 this.changeDetailsToRemoveState();
825 // check previous checked entries
826 this.checkedLogs.forEach(function (logUid) {
828 $('#forCallerList li.call[data-uid="' + logUid + '"]')
830 if (checkbox.length > 0) {
831 checkbox.attr('checked', true)
832 .data('checkboxradio')
834 $('#deleteActionBtn')
835 .removeClass('ui-disabled')
836 .attr('tabIndex', '0');
840 this.setSelectAllDetails();
842 // close popup if there are no checked checkboxes
843 if (!$("#forCallerList input:checked").length) {
844 if ($.mobile.popup.active) {
845 $.mobile.popup.active.close();
849 this.addEventsForCallerListCheckboxes();
850 // lock buttons if unknown caller
852 this.unlockButtons();
855 '#callActionBtn, #smsActionBtn, #deleteActionBtn'
862 * @param {string} accountId
864 updateCallerHeaderAccountId: function Ui_updateCallerHeaderAccountId(
867 $('.infoContainer .accountId').html(accountId);
871 * Update number of entries
872 * @param numberOfEntries
874 updateCallerHeaderNumberOfEntries:
875 function Ui_updateCallerHeaderNumberOfEntries(numberOfEntries) {
876 $('.infoContainer .numberOfEntries').html(
879 (numberOfEntries === 1 ? 'call' : 'calls')
884 * Updates caller main info
885 * @param {CallHistoryEntry} entry
886 * @param {number} numberOfEntries
888 updateCallerHeader: function Ui_updateCallerHeader(
892 // TODO need to refactor
894 var name = '', party, imgPath, personId;
896 $('#header .photo').css('background-image', this.photoURIdefault);
898 if (entry.remoteParties !== null) {
899 party = entry.remoteParties[0];
900 personId = parseInt(party.personId, 10);
901 name = this.getNameByNumber(party.remoteParty);
902 if (party.displayName) {
903 this.updateCallerHeaderAccountId(party.remoteParty);
905 this.updateCallerHeaderNumberOfEntries(numberOfEntries);
906 if (personId !== 0) {
907 imgPath = app.getPhotoURIForContact(personId);
908 if (imgPath !== false) {
910 .css('background-image', 'url(' + imgPath + ')');
913 } else if (entry.contactId !== null) {
914 name = entry.accountId;
915 this.updateCallerHeaderAccountId(entry.accountId);
916 this.updateCallerHeaderNumberOfEntries(numberOfEntries);
918 name = entry.accountId;
919 this.updateCallerHeaderAccountId('');
920 this.updateCallerHeaderNumberOfEntries(numberOfEntries);
922 $('.contact > .infoContainer > .name').html(
923 this.templateManager.modifiers.escape(name)
927 // TODO FIXME why that method is in the UI ?
928 loadContacts: function Model_loadContacts(callback) {
929 var contactsFoundCB, errorCB;
931 this.contactsLoaded = null;
933 contactsFoundCB = function (contacts) {
934 this.contactsLoaded = contacts;
935 if (callback instanceof Function) {
940 errorCB = function (error) {
942 'Model_loadContacts, problem with find() method: ' +
947 this.addressBook.find(contactsFoundCB.bind(this), errorCB);
951 * Remove search filter from content and appends it to header
953 // TODO FIXME HACK it is one big hack for web-ui-fw
954 // title need to be changed
955 removeSearchBarToHeader: function () {
956 $('#page-header').append($('#callView .ui-listview-filter'));
957 $.mobile.activePage.page('refresh');
958 $('.ui-input-cancel').remove(); // patch for WebUI bug
959 $('#calllogListContent').trigger('resize'); // WebUi scrollview fix
965 scrollToBottom: function () {
966 var scrollView = $(".ui-scrollview-view");
967 scrollView.css("-webkit-transform", "translate3d(0px, -" +
968 scrollView.height() + "px, 0px)");
972 * Binding visibility state change event
974 onVisibilityChange: function () {
976 document.addEventListener('webkitvisibilitychange', function () {
977 if (document.webkitVisibilityState === 'hidden') {
978 self.updateCheckboxes();
980 self.loadContacts(app.updateCallLists.bind(app));
981 $('#callActionBtn, #smsActionBtn')
982 .removeClass('ui-disabled');
988 * Store uid of checked entries in checkedLogs Array
990 updateCheckboxes: function Ui_updateCheckboxes() {
991 // empty checkedLogs logs array
992 var checkedLogs = this.checkedLogs = [];
993 // iterate through the entries
994 $('#forCallerList li.call').each(function () {
995 // if entry cotains checked checkbox
996 if ($(this).find('form label').hasClass('ui-checkbox-on')) {
997 var checkedEntry = $(this).data('entries')[0];
998 // push the entry uid to checkedLogs array
999 checkedLogs.push(checkedEntry.uid);
1006 * @param {jQuery} element
1008 lockButtons: function Ui_lockButtons(buttons) {
1009 $(buttons).addClass('ui-disabled').attr('tabIndex', '-1').blur();
1014 * @param {jQuery} element
1016 unlockButtons: function Ui_unlockButtons() {
1017 $('#callActionBtn, #smsActionBtn, #deleteActionBtn')
1018 .removeClass('ui-disabled')
1019 .attr('tabIndex', '0');
1024 * Patch for UI, bad refresh scrollView
1026 refreshScrollView: function () {
1027 var scrollView = $('.ui-scrollview-view'),
1028 show = function () {
1032 setTimeout(show, 0);
1036 * Handle event when last log is removed
1038 removedLastLog: function () {
1039 this.hideCheckboxes();
1040 $(".ui-popup").popup('close');
1041 $.mobile.changePage('#callView');