0 Votes

Changes for page Todolist Macro

Last modified by Ryan C on 2025/04/30 07:54

From version 14.1
edited by Ryan C
on 2025/04/24 11:07
Change comment: Todo List item added
To version 27.1
edited by Ryan C
on 2025/04/24 11:25
Change comment: There is no comment for this version

Summary

Details

Page properties
Content
... ... @@ -1,10 +1,9 @@
1 1  Macro to display todo-lists using emberjs. This code is based on the emberjs demo. You can see an example below
2 2  
3 3  {{todolist width="50%" center="1"}}
4 -a thing|1
5 -a second thing|0
6 6  a 3nd thing|0
7 -a 3nd thing|1
8 -a 3nd thing|0
5 +adding this thing|0
6 +adding this thing|0
7 +refreshed they are still there not even having to save page|0
9 9  
10 10  {{/todolist}}
XWiki.JavaScriptExtension[0]
Code
... ... @@ -1,8 +1,13 @@
1 1  requirejs.config({
2 2   baseUrl: '${xwiki.getDocument("TodoLists.TodoListMacro").getURL("download")}/',
3 3   paths: {
4 - 'jquery': 'https://code.jquery.com/jquery-3.6.0.min' // Updated jQuery version
4 + // Use existing jQuery if available, otherwise load 1.10.2
5 + 'jquery': window.jQuery ? 'empty:' : 'https://code.jquery.com/jquery-1.10.2.min'
5 5   },
7 + map: {
8 + // Map any module requiring 'jquery' to use the global jQuery
9 + '*': { 'jquery': window.jQuery ? 'jquery-global' : 'jquery' }
10 + },
6 6   shim: {
7 7   'ember-data': {
8 8   deps: ['ember', 'jquery']
... ... @@ -13,280 +13,331 @@
13 13   }
14 14  });
15 15  
21 +// Define a module that returns the global jQuery
22 +define('jquery-global', [], function() {
23 + return window.jQuery;
24 +});
25 +
26 +// Define empty module for when jQuery is already loaded
27 +define('empty:', [], function() {
28 + return window.jQuery;
29 +});
30 +
16 16  require(['jquery', 'handlebars-v1.2.1', 'ember-data', 'ember'], function($) {
17 - // Create app
18 - window.Todos = Ember.Application.create({
19 - rootElement: '#todoappdiv'
20 - });
32 + // Check jQuery version compatibility with Ember
33 + var jQueryVersion = $.fn.jquery.split('.');
34 + var majorVersion = parseInt(jQueryVersion[0]);
35 + var minorVersion = parseInt(jQueryVersion[1]);
21 21  
22 - // Define the todolist store that will write to the TodoListsService
23 - Todos.ApplicationAdapter = DS.Adapter.extend({
24 - createRecord: function(store, type, record) {
25 - console.log("createRecord");
26 - var data = this.serialize(record, { includeId: true });
27 - var query = { create: "1", content: JSON.stringify(data) };
28 -
29 - return new Ember.RSVP.Promise(function(resolve, reject) {
30 - jQuery.getJSON(
31 - "${xwiki.getURL('TodoLists.TodoListsService')}?page=" +
32 - XWiki.currentSpace + "." + XWiki.currentPage +
33 - "&xpage=plain&outputSyntax=plain",
34 - query
35 - ).then(function(data) {
36 - Ember.run(function() {
37 - resolve(data);
38 - });
39 - }, function(jqXHR) {
40 - Ember.run(function() {
41 - reject(jqXHR);
42 - });
43 - });
44 - });
45 - },
46 -
47 - deleteRecord: function(store, type, record) {
48 - console.log("deleteRecord");
49 - var recordArrays = store.recordArrayManager.recordArraysForRecord(record);
50 - if (recordArrays && recordArrays.list && recordArrays.list[0]) {
51 - return this.saveAll(recordArrays.list[0].content);
52 - }
53 - return Ember.RSVP.resolve();
54 - },
55 -
56 - find: function(store, type, id) {
57 - console.log("find");
58 - return Ember.RSVP.resolve();
59 - },
60 -
61 - findAll: function(store, type, sinceToken) {
62 - console.log("findAll");
63 - var query = { since: sinceToken };
64 -
65 - return new Ember.RSVP.Promise(function(resolve, reject) {
66 - jQuery.getJSON(
67 - "${xwiki.getURL('TodoLists.TodoListsService')}?page=" +
68 - XWiki.currentSpace + "." + XWiki.currentPage +
69 - "&xpage=plain&outputSyntax=plain",
70 - query
71 - ).then(function(data) {
72 - Ember.run(function() {
73 - resolve(data);
74 - });
75 - }, function(jqXHR) {
76 - Ember.run(function() {
77 - reject(jqXHR);
78 - });
79 - });
80 - });
81 - },
82 -
83 - updateRecord: function(store, type, record) {
84 - var recordArrays = store.recordArrayManager.recordArraysForRecord(record);
85 - if (recordArrays && recordArrays.list && recordArrays.list[0]) {
86 - return this.saveAll(recordArrays.list[0].content);
87 - }
88 - return Ember.RSVP.resolve();
89 - },
90 -
91 - saveAll: function(alldata) {
92 - console.log("saveAll");
93 - var query = { save: "1", content: JSON.stringify(alldata) };
94 -
95 - return new Ember.RSVP.Promise(function(resolve, reject) {
96 - jQuery.getJSON(
97 - "${xwiki.getURL('TodoLists.TodoListsService')}?page=" +
98 - XWiki.currentSpace + "." + XWiki.currentPage +
99 - "&xpage=plain&outputSyntax=plain",
100 - query
101 - ).then(function(data) {
102 - Ember.run(function() {
103 - resolve(data);
104 - });
105 - }, function(jqXHR) {
106 - Ember.run(function() {
107 - reject(jqXHR);
108 - });
109 - });
110 - });
111 - }
112 - });
37 + console.log("Using jQuery version:", $.fn.jquery);
113 113  
114 - // Register the adapter
115 - Todos.ApplicationAdapter.reopen({
116 - namespace: 'api'
117 - });
39 + // Verify jQuery version is compatible with Ember
40 + var isCompatible = (majorVersion === 1 && minorVersion >= 7 && minorVersion <= 10) ||
41 + (majorVersion === 2 && minorVersion === 0);
118 118  
119 - // Routing
120 - Todos.Router.map(function() {
121 - this.resource('todos', { path: '' }, function() {
122 - this.route('active');
123 - this.route('completed');
43 + if (!isCompatible) {
44 + console.error("Warning: Ember Views require jQuery 1.7, 1.8, 1.9, 1.10, or 2.0. Current version:", $.fn.jquery);
45 + // Try to load a compatible version if needed
46 + require(['https://code.jquery.com/jquery-1.10.2.min.js'], function(compatibleJQuery) {
47 + // Store the original jQuery
48 + var originalJQuery = window.jQuery;
49 + // Replace with compatible jQuery temporarily
50 + window.jQuery = compatibleJQuery;
51 + // Continue with the rest of the code
52 + initializeEmberApp(compatibleJQuery);
53 + // Restore original jQuery when done
54 + window.jQuery = originalJQuery;
124 124   });
125 - });
56 + } else {
57 + // jQuery version is compatible, proceed normally
58 + initializeEmberApp($);
59 + }
126 126  
127 - Todos.TodosRoute = Ember.Route.extend({
128 - model: function() {
129 - return this.store.find('todo');
130 - }
131 - });
61 + function initializeEmberApp($) {
62 + // Create app
63 + window.Todos = Ember.Application.create({
64 + rootElement: '#todoappdiv'
65 + });
132 132  
133 - Todos.TodosIndexRoute = Ember.Route.extend({
134 - model: function() {
135 - return this.modelFor('todos');
136 - }
137 - });
67 + // Define the todolist store that will write to the TodoListsService
68 + Todos.ApplicationAdapter = DS.Adapter.extend({
69 + namespace: 'api', // Moved inside the adapter definition
138 138  
139 - Todos.TodosActiveRoute = Ember.Route.extend({
140 - model: function() {
141 - return this.store.filter('todo', function(todo) {
142 - return !todo.get('isCompleted');
143 - });
144 - },
145 - renderTemplate: function(controller) {
146 - this.render('todos/index', { controller: controller });
147 - }
148 - });
71 + createRecord: function(store, type, record) {
72 + console.log("createRecord");
73 + var data = this.serialize(record, { includeId: true });
74 + var query = { create: "1", content: JSON.stringify(data) };
149 149  
150 - Todos.TodosCompletedRoute = Ember.Route.extend({
151 - model: function() {
152 - return this.store.filter('todo', function(todo) {
153 - return todo.get('isCompleted');
154 - });
155 - },
156 - renderTemplate: function(controller) {
157 - this.render('todos/index', { controller: controller });
158 - }
159 - });
76 + return new Ember.RSVP.Promise(function(resolve, reject) {
77 + var url = "${xwiki.getURL('TodoLists.TodoListsService')}?page=" +
78 + XWiki.currentSpace + "." + XWiki.currentPage +
79 + "&xpage=plain&outputSyntax=plain";
80 + console.log("Creating record, URL:", url);
160 160  
161 - // Data model
162 - Todos.Todo = DS.Model.extend({
163 - title: DS.attr('string'),
164 - priority: DS.attr('string'),
165 - assignee: DS.attr('string'),
166 - isCompleted: DS.attr('boolean')
167 - });
82 + jQuery.getJSON(url, query).then(function(data) {
83 + console.log("Create success:", data);
84 + Ember.run(function() {
85 + resolve(data);
86 + });
87 + }, function(jqXHR) {
88 + console.error("Create error:", jqXHR);
89 + Ember.run(function() {
90 + reject(jqXHR);
91 + });
92 + });
93 + });
94 + },
168 168  
169 - // Controller
170 - Todos.TodoController = Ember.ObjectController.extend({
171 - actions: {
172 - editTodo: function() {
173 - this.set('isEditing', true);
96 + deleteRecord: function(store, type, record) {
97 + console.log("deleteRecord");
98 + var recordArrays = store.recordArrayManager.recordArraysForRecord(record);
99 + if (recordArrays && recordArrays.list && recordArrays.list[0]) {
100 + return this.saveAll(recordArrays.list[0].content);
101 + }
102 + return Ember.RSVP.resolve();
174 174   },
175 - acceptChanges: function() {
176 - this.set('isEditing', false);
177 177  
178 - if (Ember.isEmpty(this.get('model.title'))) {
179 - this.send('removeTodo');
180 - } else {
181 - this.get('model').save();
105 + find: function(store, type, id) {
106 + console.log("find");
107 + return Ember.RSVP.resolve();
108 + },
109 +
110 + findAll: function(store, type, sinceToken) {
111 + console.log("findAll");
112 + var query = { since: sinceToken };
113 +
114 + return new Ember.RSVP.Promise(function(resolve, reject) {
115 + var url = "${xwiki.getURL('TodoLists.TodoListsService')}?page=" +
116 + XWiki.currentSpace + "." + XWiki.currentPage +
117 + "&xpage=plain&outputSyntax=plain";
118 + console.log("Finding all records, URL:", url);
119 +
120 + jQuery.getJSON(url, query).then(function(data) {
121 + console.log("FindAll success:", data);
122 + Ember.run(function() {
123 + resolve(data);
124 + });
125 + }, function(jqXHR) {
126 + console.error("FindAll error:", jqXHR);
127 + Ember.run(function() {
128 + reject(jqXHR);
129 + });
130 + });
131 + });
132 + },
133 +
134 + updateRecord: function(store, type, record) {
135 + console.log("updateRecord");
136 + var recordArrays = store.recordArrayManager.recordArraysForRecord(record);
137 + if (recordArrays && recordArrays.list && recordArrays.list[0]) {
138 + return this.saveAll(recordArrays.list[0].content);
182 182   }
140 + return Ember.RSVP.resolve();
183 183   },
184 - removeTodo: function() {
185 - var todo = this.get('model');
186 - todo.deleteRecord();
187 - todo.save();
188 - }
189 - },
190 190  
191 - isEditing: false,
143 + saveAll: function(alldata) {
144 + console.log("saveAll");
145 + var query = { save: "1", content: JSON.stringify(alldata) };
192 192  
193 - isCompleted: function(key, value) {
194 - var model = this.get('model');
147 + return new Ember.RSVP.Promise(function(resolve, reject) {
148 + var currentPage = XWiki.currentSpace + "." + XWiki.currentPage;
149 + var url = "${xwiki.getURL('TodoLists.TodoListsService')}?page=" +
150 + currentPage + "&xpage=plain&outputSyntax=plain";
151 + console.log("Saving all records, URL:", url, "Page:", currentPage);
195 195  
196 - if (value === undefined) {
197 - // Property being used as a getter
198 - return model.get('isCompleted');
199 - } else {
200 - // Property being used as setter
201 - model.set('isCompleted', value);
202 - model.save();
203 - return value;
153 + jQuery.getJSON(url, query).then(function(data) {
154 + console.log("SaveAll success:", data);
155 + Ember.run(function() {
156 + resolve(data);
157 + });
158 + }, function(jqXHR) {
159 + console.error("SaveAll error:", jqXHR);
160 + Ember.run(function() {
161 + reject(jqXHR);
162 + });
163 + });
164 + });
204 204   }
205 - }.property('model.isCompleted')
206 - });
166 + });
207 207  
208 - Todos.TodosController = Ember.ArrayController.extend({
209 - actions: {
210 - createTodo: function() {
211 - // Get the todo title set by the "New Todo" text field
212 - var title = this.get('newTitle');
213 - if (!title || !title.trim()) { return; }
168 + // Routing
169 + Todos.Router.map(function() {
170 + this.resource('todos', { path: '' }, function() {
171 + this.route('active');
172 + this.route('completed');
173 + });
174 + });
214 214  
215 - // Create the new Todo model
216 - var todo = this.store.createRecord('todo', {
217 - title: title,
218 - priority: "P3",
219 - assignee: "all",
220 - isCompleted: false
221 - });
176 + Todos.TodosRoute = Ember.Route.extend({
177 + model: function() {
178 + return this.store.find('todo');
179 + }
180 + });
222 222  
223 - // Clear the "New Todo" text field
224 - this.set('newTitle', '');
182 + Todos.TodosIndexRoute = Ember.Route.extend({
183 + model: function() {
184 + return this.modelFor('todos');
185 + }
186 + });
225 225  
226 - // Save the new model
227 - todo.save().then(function() {
228 - // Force a refresh of the list after saving
229 - Ember.run.later(function() {
230 - Todos.store.find('todo');
231 - }, 100);
188 + Todos.TodosActiveRoute = Ember.Route.extend({
189 + model: function() {
190 + return this.store.filter('todo', function(todo) {
191 + return !todo.get('isCompleted');
232 232   });
233 233   },
234 -
235 - clearCompleted: function() {
236 - var completed = this.filter(function(todo) {
194 + renderTemplate: function(controller) {
195 + this.render('todos/index', { controller: controller });
196 + }
197 + });
198 +
199 + Todos.TodosCompletedRoute = Ember.Route.extend({
200 + model: function() {
201 + return this.store.filter('todo', function(todo) {
237 237   return todo.get('isCompleted');
238 238   });
239 -
240 - completed.forEach(function(todo) {
204 + },
205 + renderTemplate: function(controller) {
206 + this.render('todos/index', { controller: controller });
207 + }
208 + });
209 +
210 + // Data model
211 + Todos.Todo = DS.Model.extend({
212 + title: DS.attr('string'),
213 + priority: DS.attr('string'),
214 + assignee: DS.attr('string'),
215 + isCompleted: DS.attr('boolean')
216 + });
217 +
218 + // Controller
219 + Todos.TodoController = Ember.ObjectController.extend({
220 + actions: {
221 + editTodo: function() {
222 + this.set('isEditing', true);
223 + },
224 + acceptChanges: function() {
225 + this.set('isEditing', false);
226 +
227 + if (Ember.isEmpty(this.get('model.title'))) {
228 + this.send('removeTodo');
229 + } else {
230 + this.get('model').save();
231 + }
232 + },
233 + removeTodo: function() {
234 + var todo = this.get('model');
241 241   todo.deleteRecord();
242 242   todo.save();
243 - });
244 - }
245 - },
237 + }
238 + },
246 246  
247 - remaining: function() {
248 - return this.filter(function(todo) {
249 - return !todo.get('isCompleted');
250 - }).get('length');
251 - }.property('@each.isCompleted'),
240 + isEditing: false,
252 252  
253 - inflection: function() {
254 - var remaining = this.get('remaining');
255 - return remaining === 1 ? 'item' : 'items';
256 - }.property('remaining'),
257 -
258 - hasCompleted: function() {
259 - return this.get('completed') > 0;
260 - }.property('completed'),
242 + isCompleted: function(key, value) {
243 + var model = this.get('model');
261 261  
262 - completed: function() {
263 - return this.filter(function(todo) {
264 - return todo.get('isCompleted');
265 - }).get('length');
266 - }.property('@each.isCompleted'),
245 + if (value === undefined) {
246 + // Property being used as a getter
247 + return model.get('isCompleted');
248 + } else {
249 + // Property being used as setter
250 + model.set('isCompleted', value);
251 + model.save();
252 + return value;
253 + }
254 + }.property('model.isCompleted')
255 + });
267 267  
268 - allAreDone: function(key, value) {
269 - if (value === undefined) {
270 - return this.get('length') > 0 && this.every(function(todo) {
257 + Todos.TodosController = Ember.ArrayController.extend({
258 + actions: {
259 + createTodo: function() {
260 + // Get the todo title set by the "New Todo" text field
261 + var title = this.get('newTitle');
262 + if (!title || !title.trim()) { return; }
263 +
264 + console.log("Creating new todo with title:", title);
265 +
266 + // Create the new Todo model
267 + var todo = this.store.createRecord('todo', {
268 + title: title,
269 + priority: "P3",
270 + assignee: "all",
271 + isCompleted: false
272 + });
273 +
274 + // Clear the "New Todo" text field
275 + this.set('newTitle', '');
276 +
277 + // Save the new model
278 + todo.save().then(function(savedTodo) {
279 + console.log("Todo saved successfully:", savedTodo);
280 + // Force a refresh of the list after saving
281 + Ember.run.later(function() {
282 + console.log("Refreshing todo list");
283 + Todos.store.find('todo');
284 + }, 100);
285 + }, function(error) {
286 + console.error("Failed to save todo:", error);
287 + });
288 + },
289 +
290 + clearCompleted: function() {
291 + var completed = this.filter(function(todo) {
292 + return todo.get('isCompleted');
293 + });
294 +
295 + completed.forEach(function(todo) {
296 + todo.deleteRecord();
297 + todo.save();
298 + });
299 + }
300 + },
301 +
302 + remaining: function() {
303 + return this.filter(function(todo) {
304 + return !todo.get('isCompleted');
305 + }).get('length');
306 + }.property('@each.isCompleted'),
307 +
308 + inflection: function() {
309 + var remaining = this.get('remaining');
310 + return remaining === 1 ? 'item' : 'items';
311 + }.property('remaining'),
312 +
313 + hasCompleted: function() {
314 + return this.get('completed') > 0;
315 + }.property('completed'),
316 +
317 + completed: function() {
318 + return this.filter(function(todo) {
271 271   return todo.get('isCompleted');
272 - });
273 - } else {
274 - this.forEach(function(todo) {
275 - todo.set('isCompleted', value);
276 - todo.save();
277 - });
278 - return value;
279 - }
280 - }.property('@each.isCompleted')
281 - });
320 + }).get('length');
321 + }.property('@each.isCompleted'),
282 282  
283 - // Editing
284 - Todos.EditTodoView = Ember.TextField.extend({
285 - didInsertElement: function() {
286 - this.$().focus();
287 - }
288 - });
323 + allAreDone: function(key, value) {
324 + if (value === undefined) {
325 + return this.get('length') > 0 && this.every(function(todo) {
326 + return todo.get('isCompleted');
327 + });
328 + } else {
329 + this.forEach(function(todo) {
330 + todo.set('isCompleted', value);
331 + todo.save();
332 + });
333 + return value;
334 + }
335 + }.property('@each.isCompleted')
336 + });
289 289  
290 - Ember.Handlebars.helper('edit-todo', Todos.EditTodoView);
338 + // Editing
339 + Todos.EditTodoView = Ember.TextField.extend({
340 + didInsertElement: function() {
341 + this.$().focus();
342 + }
343 + });
344 +
345 + Ember.Handlebars.helper('edit-todo', Todos.EditTodoView);
346 + }
291 291  });
292 292