diff --git a/.gitignore b/.gitignore deleted file mode 100644 index b512c09..0000000 --- a/.gitignore +++ /dev/null @@ -1 +0,0 @@ -node_modules \ No newline at end of file diff --git a/README.md b/README.md index eaff206..95c9158 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,6 @@ Firebase adapter for Webix UI ============================= -[![npm version](https://badge.fury.io/js/webix-firebase.svg)](https://badge.fury.io/js/webix-firebase) - Library allows using [Webix](http://webix.com) components with [FireBase](https://firebase.google.com/) Latest version will work with Webix 6.2+, if you are using an older version of Webix, you need to use version 0.5 of this package. @@ -21,66 +19,63 @@ Include Webix and Firebase files on the page ```html - - + + - - - - + + + + import { initializeApp } from "https://www.gstatic.com/firebasejs/12.6.0/firebase-app.js"; + import { getFirestore, collection } from "https://www.gstatic.com/firebasejs/12.6.0/firebase-firestore.js"; + ``` -Create main firebase connection +Create a main firebase connection ```js -firebase.initializeApp({ +const app = initializeApp({ apiKey: "YOU API KEY HERE", projectId: "webix-demo" }); -// create firebase connection, and assign it to webix -var db = firebase.firestore(); -db.settings({ timestampsInSnapshots:true }); - -webix.firestore = db; +// create a firebase connection, and assign it to webix +webix.firestore = getFirestore(app); ``` -Init webix component, using "firebase->{reference}" as data url +Init webix component, using "firebase->{reference}" as a data url ```js webix.ui({ id:"dtable", view:"datatable", autoConfig:true, - // load data from "books" collection url: "firestore->books", // save data to "books" collection save:"firestore->books" } -``` +``` That is it. -Adding "url" property will enable data loading and automatic updates of component when data changed in the firebase. +Adding "url" property will enable data loading and automatic updates of component when data changed in the firebase. -Adding "save" property ensures that all changes in the datatable will be saved to the Firebase +Adding "save" property ensures that all changes in the datatable will be saved to the Firebase. ### Using FireBase references -Instead of using text url you can use firestore collections directly +Instead of using a text url you can use firestore collections directly ```js -firebase.initializeApp({ +const app = initializeApp({ apiKey: "YOU API KEY HERE", projectId: "webix-demo" }); -// create firebase connection, and assign it to webix -var db = firebase.firestore(); -db.settings({ timestampsInSnapshots:true }); +// create a firebase connection, and assign it to webix +const db = getFirestore(app); -var proxy = webix.proxy("firestore", db.collection("books")); +const proxy = webix.proxy("firestore", collection(db, "books")); webix.ui({ view:"list", @@ -92,7 +87,7 @@ webix.ui({ ### Dynamic data loading -You can use "load" command to (re)load data in the component. +You can use "load" command to (re)load data in the component. ```js $$("dtable").clearAll(); @@ -114,27 +109,29 @@ Include Webix and Firebase files on the page ```html - - + + - - - - + + + + import { initializeApp } from "https://www.gstatic.com/firebasejs/12.6.0/firebase-app.js"; + import { getDatabase, ref } from "https://www.gstatic.com/firebasejs/12.6.0/firebase-database.js"; + ``` Create main firebase connection ```js -firebase.initializeApp({ - databaseURL: "https://webix-demo.firebaseio.com/" +const app = initializeApp({ + databaseURL: "https://webix-demo.firebaseio.com/" }); -// create firebase connection, and assign it to webix -webix.firebase = firebase.database(); +// create a firebase connection, and assign it to webix +webix.firebase = getDatabase(app); ``` -Init webix component, using "firebase->{reference}" as data url +Init webix component, using "firebase->{reference}" as a data url ```js webix.ui({ @@ -146,32 +143,31 @@ webix.ui({ },{ id:"author", editor:"text", fillspace:1 }], - // load data from /books url: "firebase->books", // save data to /books save:"firebase->books" -} -``` +}) +``` That is it. -Adding "url" property will enable data loading and automatic updates of component when data changed in the firebase. +Adding a "url" property will enable data loading and automatic updates of component when data changed in the firebase. -Adding "save" property ensures that all changes in the datatable will be saved to the Firebase +Adding "save" property ensures that all changes in the datatable will be saved to the Firebase. ### Using FireBase references -Instead of using text url you can use firebase references directly +Instead of using a text url you can use firebase references directly ```js -firebase.initializeApp({ +const app = initializeApp({ databaseURL: "https://webix-demo.firebaseio.com/" }); -var db = firebase.database(); -var proxy = webix.proxy("firebase", db.ref("books")); +const db = getDatabase(app); +const proxy = webix.proxy("firebase", ref(db, "books")); webix.ui({ view:"list", @@ -183,7 +179,7 @@ webix.ui({ ### Dynamic data loading -You can use "load" command to (re)load data in the component. +You can use "load" command to (re)load data in the component. ```js $$("dtable").clearAll(); @@ -198,20 +194,18 @@ $$("dtable").load( webix.proxy("firebase", ref) ); ``` - - ### Sync api -Webix components have native [sync](http://docs.webix.com/api__link__ui.proto_sync.html) api to [sync data between components](http://docs.webix.com/desktop__data_binding.html). The same api can be used with firebase +Webix components have native [sync](http://docs.webix.com/api__link__ui.proto_sync.html) api to [sync data between components](http://docs.webix.com/desktop__data_binding.html). The same api can be used with firebase: ```js -firebase.initializeApp({ +const app = initializeApp({ databaseURL: "https://webix-demo.firebaseio.com/" }); -webix.firebase = firebase.database(); -var ref = webix.firebase.ref("books"); +webix.firebase = getDatabase(app); +const source = ref(webix.firebase, "books"); -$$("dtable").sync(ref); +$$("dtable").sync(source); ``` ### Working with Forms and Templates @@ -232,13 +226,13 @@ $$("template").load("books/4") In some cases, it has sense to not load data correctly but bind form ( template ) to some other view or data collection ```js -var data = new webix.DataCollection({ +const data = new webix.DataCollection({ url:"firebase->books", save:"firebase->books" }) form.bind(data); data.waitData.then(function(){ - //you need to use setCursor API to load some record from collection into a form + // you need to use a setCursor API to load some record from a collection into a form data.setCursor("4"); }) ``` @@ -251,7 +245,7 @@ webix.ui({ url:"firebase->books", save:"firebase->books" }); -form.bind("d1"); //selected row will be shown in a form +form.bind("d1"); // a selected row will be shown in a form ``` Samples @@ -269,7 +263,7 @@ License The MIT License -Copyright (c) 2015 Maksim Kozhukh +Copyright (c) 2025 Maksim Kozhukh Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/bower.json b/bower.json deleted file mode 100644 index 83a8d92..0000000 --- a/bower.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "name": "webix-firebase", - "version": "0.2.0", - "homepage": "https://github.com/webix-hub/webix-firebase", - "authors": [ - "Maksim Kozhukh " - ], - "description": "Firebase adapter for Webix UI", - "main": "codebase/webix-firebase.js", - "keywords": [ - "webix", - "firebase" - ], - "license": "MIT", - "ignore": [ - "**/.*", - "node_modules", - "bower_components", - "test", - "tests" - ] -} diff --git a/codebase/webix-firebase.js b/codebase/webix-firebase.js index daa9945..63aa96e 100644 --- a/codebase/webix-firebase.js +++ b/codebase/webix-firebase.js @@ -1,10 +1,15 @@ /* Firebase proxy for Webix - Allows to use firebase urls in all places where normal http urls can be used + Allows to use firebase urls in all places where normal https urls can be used */ +import { + ref, onChildChanged, onChildAdded, onChildRemoved, + child, get, update, push, set +} from "https://www.gstatic.com/firebasejs/12.6.0/firebase-database.js"; + webix.proxy.firebase = { - $proxy:true, + $proxy: true, /* some.load("firebase->ref"); or @@ -12,108 +17,103 @@ webix.proxy.firebase = { or url:"firebase->ref" */ - load:function(view, callback){ - //decode string reference if necessary + load: function (view, params) { + // decode a string reference if necessary if (typeof this.source == "object") this.collection = this.source; else - this.collection = this.collection || webix.firebase.ref(this.source); - - var data = webix.promise.defer(); - - //full data loading - do only once, during first loading - this.collection.once("value", webix.bind(function(pack){ - var isCollection = !!view.exists; - - if (isCollection){ - var result = []; - //preserve data order - pack.forEach(function(child){ - var record = child.val(); - - //convert simple string types to data objects + this.collection = this.collection || ref(webix.firebase, this.source); + + return get(this.collection).then((pack) => { + const isCollection = !!view.exists; + let result; + + if (isCollection) { + result = []; + // preserve a data order + pack.forEach(function (child) { + let record = child.val(); + // convert simple string types to data objects if (typeof record !== "object") - record = { value : record }; + record = { value: record }; record.id = child.key; result.push(record); }); } else { - var result = pack.val(); + result = pack.val(); } - data.resolve(result); this._setHandlers(view); if (isCollection) - this._setAddRemove(view); + setTimeout(() => { this._setAddRemove(view) }) else this._addSaveMethod(view); - }, this)); - - return data; + return result; + }).catch((error) => { + console.error("Error retrieving data:", error); + }); }, - _addSaveMethod:function(view){ - var proxy = this; - view.save = function(){ - var values = this.getDirtyValues(); + _addSaveMethod: function (view) { + const proxy = this; + view.save = function () { + const values = this.getDirtyValues(); this.setDirty(false); - proxy.collection.update(values); + update(proxy.collection, values); }; }, - _setHandlers:function(view){ - //after initial data loading, set listeners for changes - //data in firebase updated - this.collection.on("child_changed", function(data){ - //event triggered by data saving in the same component + _setHandlers: function (view) { + // after initial data loading, set listeners for changes + // data in firebase updated + onChildChanged(this.collection, (data) => { + // event triggered by data saving in the same component if (view.firebase_saving) return; - var isCollection = !!view.exists; + const isCollection = !!view.exists; - if (isCollection){ - var obj = data.val(); + if (isCollection) { + const obj = data.val(); obj.id = data.key; - //do not trigger data saving events - webix.dp(view).ignore(function(){ + // do not trigger data saving events + webix.dp(view).ignore(function () { view.updateItem(obj.id, obj); }); } else { - if (view.setValues){ - var update = {} + if (view.setValues) { + const update = {} update[data.key] = data.val(); return view.setValues(update, true); } } }); }, - - _setAddRemove:function(view){ - //data in firebase added - this.collection.on("child_added", function(data){ - //event triggered by data saving in the same component + _setAddRemove: function (view) { + // data in firebase added + onChildAdded(this.collection, (data) => { + // event triggered by data saving in the same component if (view.firebase_saving) return; - //we already have record with such id - //it seems, this event duplicates info from on:value + // we already have a record with such an id + // it seems this event duplicates info from on:value if (view.exists(data.key)) return; - var obj = data.val(); + const obj = data.val(); obj.id = data.key; - //do not trigger data saving events - webix.dp(view).ignore(function(){ + // do not trigger data saving events + webix.dp(view).ignore(function () { view.add(obj); }); }); - - //data in firebase removed - this.collection.on("child_removed", function(data){ - //event triggered by data saving in the same component + // data in firebase removed + onChildRemoved(this.collection, (data) => { + // event triggered by data saving in the same component if (view.firebase_saving) return; - //do not trigger data saving events - webix.dp(view).ignore(function(){ + // do not trigger data saving events + webix.dp(view).ignore(function () { view.remove(data.key); }); }); @@ -123,62 +123,57 @@ webix.proxy.firebase = { or webix.dp(view).define("url", "firebase->ref"); */ - save:function(view, obj, dp, callback){ + save: function (view, params, dp) { //decode string reference if necessary if (typeof this.source == "object") this.collection = this.source; else - this.collection = this.collection || webix.firebase.ref(this.source); + this.collection = this.collection || ref(webix.firebase, this.source); - //flag to prevent triggering of onchange listeners on the same component + // a flag to prevent triggering of onchange listeners on the same component view.firebase_saving = true; - var result = webix.promise.defer(); - - delete obj.data.id; - if (obj.operation == "update"){ - //data changed - this.collection.child(obj.id).update(obj.data, function(error){ - if (error) - result.reject(error); - else - result.resolve({}); - }); - } else if (obj.operation == "insert"){ - //data added - var id = this.collection.push(obj.data, function(error){ - if (error) - result.reject(error); - else - result.resolve({ newid: id }); - }).key; - - } else if (obj.operation == "delete"){ - //data removed - this.collection.child(obj.id).set(null, function(error){ - if (error) - result.reject(error); - else - result.resolve({}, -1); - }); + delete params.data.id; + // fix for: object values passed to firebase cannot be undefined + Object.keys(params.data).forEach(key => params.data[key] === undefined && delete params.data[key]); + + if (params.operation === "update") { + // data changed + const childRef = child(this.collection, params.id); + return update(childRef, params.data).then(() => { + view.firebase_saving = false; + return {}; + }).catch((error) => console.error("Error updating data:", error)) + + } else if (params.operation === "insert") { + // data added + const newChildRef = push(this.collection); + return set(newChildRef, params.data).then(() => { + view.firebase_saving = false; + return { newid: newChildRef.key }; + }).catch((error) => console.error("Error inserting data:", error)) + + } else if (params.operation === "delete") { + // data removed + const childRef = child(this.collection, params.id); + return set(childRef, null).then(() => { + view.firebase_saving = false; + return {}; + }).catch((error) => console.error("Error deleting data:", error)) } - - view.firebase_saving = false; - return result; } }; - - /* Helper for component.sync(reference) */ -webix.attachEvent("onSyncUnknown", function(target, source){ - var fb = window.firebase || webix.firebase; - if (fb && source instanceof fb.database.Reference){ +webix.attachEvent("onSyncUnknown", function (target, source) { + if (!webix.firebase) return; + const tempRef = ref(webix.firebase); - var proxy = webix.proxy("firebase", source); + if (source instanceof tempRef.constructor) { + const proxy = webix.proxy("firebase", source); target = webix.$$(target.owner); target.clearAll(); diff --git a/codebase/webix-firestore.js b/codebase/webix-firestore.js index 139f208..674538d 100644 --- a/codebase/webix-firestore.js +++ b/codebase/webix-firestore.js @@ -1,94 +1,102 @@ /* - Firebase proxy for Webix - Allows to use firebase urls in all places where normal http urls can be used + Firestore proxy for Webix + Allows to use firebase urls in all places where normal https urls can be used */ +import { + collection, CollectionReference, onSnapshot, + doc, updateDoc, addDoc, deleteDoc +} from "https://www.gstatic.com/firebasejs/12.6.0/firebase-firestore.js"; + webix.proxy.firestore = { - $proxy:true, + $proxy: true, /* - some.load("firebase->ref"); + some.load("firestore->ref"); or - some.load( webix.proxy("firebase", reference) ) + some.load( webix.proxy("firestore", reference) ) or - url:"firebase->ref" + url:"firestore->ref" */ - load:function(view, callback){ - if (this._unsubscribe){ + load: function (view, params) { + if (this._unsubscribe) { this.release(); } - var data = webix.promise.defer(); + const data = webix.promise.defer(); - //decode string reference if necessary + // decode a string reference if necessary if (typeof this.source == "object") this.collection = this.source; else - this.collection = this.collection || webix.firestore.collection(this.source); - - this._unsubscribe = this.collection.onSnapshot(function(query) { - if (query.metadata.hasPendingWrites) return; + this.collection = this.collection || collection(webix.firestore, this.source); - webix.dp(view).ignore(function(){ + this._unsubscribe = onSnapshot(this.collection, function (query) { + if (query.metadata.hasPendingWrites) return; - var queue = []; - query.docChanges().forEach(function(change) { - var data = webix.copy(change.doc.data()); + webix.dp(view).ignore(function () { + const queue = []; + query.docChanges().forEach(function (change) { + const data = webix.copy(change.doc.data()); data.id = change.doc.id; - switch(change.type){ - case "added": - if (!view.exists(data.id)) - queue.push(data); //collecting data batch - break; - case "modified": - view.updateItem(data.id, data); - break; + switch (change.type) { + case "added": + if (!view.exists(data.id)) + queue.push(data); // collecting data batch + break; + case "modified": + view.updateItem(data.id, data); + break; case "removed": if (view.exists(data.id)) - view.remove(data.id); - break; + view.remove(data.id); + break; } }); - //batch adding - if (queue.length){ + // batch adding + if (queue.length) { data.resolve(queue); } }); }); - + return data; }, /* - save:"firebase->ref" + save:"firestore->ref" or - webix.dp(view).define("url", "firebase->ref"); + webix.dp(view).define("url", "firestore->ref"); */ - save:function(view, obj, dp, callback){ - //decode string reference if necessary + save: function (view, params, dp) { + // decode a string reference if necessary if (typeof this.source == "object") this.collection = this.source; else - this.collection = this.collection || webix.firestore.collection(this.source); + this.collection = this.collection || collection(webix.firestore, this.source); - delete obj.data.id; + delete params.data.id; + // fix for: object values passed to firebase cannot be undefined + Object.keys(params.data).forEach(key => params.data[key] === undefined && delete params.data[key]); - if (obj.operation == "update"){ - //data changed - return this.collection.doc(obj.id).update(obj.data); + if (params.operation == "update") { + // data changed + const docRef = doc(this.collection, params.id); + return updateDoc(docRef, params.data); - } else if (obj.operation == "insert"){ - //data added - return this.collection.add(obj.data); - - } else if (obj.operation == "delete"){ - //data removed - return this.collection.doc(obj.id).delete(); + } else if (params.operation == "insert") { + // data added + return addDoc(this.collection, params.data); + + } else if (params.operation == "delete") { + // data removed + const docRef = doc(this.collection, params.id); + return deleteDoc(docRef); } - + return webix.promise.reject(); }, - release(){ - if (this._unsubscribe){ + release() { + if (this._unsubscribe) { this._unsubscribe(); this._unsubscribe = null; } @@ -101,11 +109,11 @@ webix.proxy.firestore = { Helper for component.sync(reference) */ -webix.attachEvent("onSyncUnknown", function(target, source){ - var fb = webix.firestore || (window.firebase && window.firebase.firestore); - if (fb && source instanceof fb.firestore.Collection){ +webix.attachEvent("onSyncUnknown", function (target, source) { + if (!webix.firestore) return; - var proxy = webix.proxy("firestore", source); + if (source instanceof CollectionReference) { + const proxy = webix.proxy("firestore", source); target = webix.$$(target.owner); target.clearAll(); diff --git a/package.json b/package.json index ef280fc..d91f99c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "webix-firebase", - "version": "0.6.0", + "version": "0.7.0", "description": "Firebase adapter for Webix UI", "directories": {}, "scripts": {}, @@ -13,7 +13,7 @@ "bugs": { "url": "https://github.com/webix-hub/webix-firebase/issues" }, - "homepage": "http://webix.com", + "homepage": "https://webix.com", "dependencies": {}, "devDependencies": {} } diff --git a/samples/01_datatable_url.html b/samples/01_datatable_url.html index fb9f6c7..c6b5d41 100644 --- a/samples/01_datatable_url.html +++ b/samples/01_datatable_url.html @@ -1,29 +1,26 @@ - - + + - - - - - - + Firebase and Webix Datatable - saving and loading by url - \ No newline at end of file diff --git a/samples/02_datatable_ref.html b/samples/02_datatable_ref.html index 0cf8773..3e6b64a 100644 --- a/samples/02_datatable_ref.html +++ b/samples/02_datatable_ref.html @@ -1,33 +1,29 @@ - - + + - - - - - - + Firebase and Webix Datatable - saving and loading by reference - \ No newline at end of file diff --git a/samples/03_datatable_sync.html b/samples/03_datatable_sync.html index ab79af5..6509cdf 100644 --- a/samples/03_datatable_sync.html +++ b/samples/03_datatable_sync.html @@ -1,22 +1,19 @@ - - + + - - - - - - + Firebase and Webix Datatable - syncing with firebase - diff --git a/samples/04_other_components.html b/samples/04_other_components.html index ad9fccd..e680b6c 100644 --- a/samples/04_other_components.html +++ b/samples/04_other_components.html @@ -1,29 +1,29 @@ - - + + - - - - - - + Firebase and Webix Datatable - syncing with firebase - \ No newline at end of file diff --git a/samples/05_kanban.html b/samples/05_kanban.html index 7262757..c8a1d84 100644 --- a/samples/05_kanban.html +++ b/samples/05_kanban.html @@ -1,29 +1,28 @@ - - - - + + + + - - - - - - + Firebase and Webix Datatable - syncing with firebase - \ No newline at end of file diff --git a/samples/06_datatable_reload.html b/samples/06_datatable_reload.html index 63f0b50..e6037d5 100644 --- a/samples/06_datatable_reload.html +++ b/samples/06_datatable_reload.html @@ -1,29 +1,26 @@ - - + + - - - - - - + Firebase and Webix Datatable - reloading data from a different collection - \ No newline at end of file diff --git a/samples/07_form.html b/samples/07_form.html index d74133a..65b0d44 100644 --- a/samples/07_form.html +++ b/samples/07_form.html @@ -1,27 +1,25 @@ - - + + - - - - - - + Firebase and Webix Form - syncing with firebase - \ No newline at end of file diff --git a/samples/08_template.html b/samples/08_template.html index 60f2fc9..08f2da1 100644 --- a/samples/08_template.html +++ b/samples/08_template.html @@ -1,27 +1,25 @@ - - + + - - - - - - + Firebase and Webix Template - syncing with firebase - \ No newline at end of file diff --git a/samples/09_options.html b/samples/09_options.html index 7c8ec13..dff2c9b 100644 --- a/samples/09_options.html +++ b/samples/09_options.html @@ -1,29 +1,26 @@ - - + + - - - - - - + Firebase and Webix Datatable - options in form and in datatable - \ No newline at end of file diff --git a/samples/10_complex_options.html b/samples/10_complex_options.html index f4fc0a4..4424065 100644 --- a/samples/10_complex_options.html +++ b/samples/10_complex_options.html @@ -1,59 +1,57 @@ - - + + - - - - - + Firebase and Webix Datatable - complex options in form and in datatable - \ No newline at end of file diff --git a/samples/11_query.html b/samples/11_query.html index 956b57e..4d97b28 100644 --- a/samples/11_query.html +++ b/samples/11_query.html @@ -1,31 +1,28 @@ - - + + - - - - - - + Firebase and Webix Datatable - using Query - \ No newline at end of file diff --git a/samples/12_file_upload.html b/samples/12_file_upload.html index c6184ae..8a42da9 100644 --- a/samples/12_file_upload.html +++ b/samples/12_file_upload.html @@ -1,49 +1,50 @@ - - + + - - - - - - - + Firebase and Webix Datatable - File Upload
- \ No newline at end of file diff --git a/samples/13_firestore_url.html b/samples/13_firestore_url.html index c6402e2..e3e971e 100644 --- a/samples/13_firestore_url.html +++ b/samples/13_firestore_url.html @@ -1,31 +1,27 @@ - - + + - - - - - - - Firebase and Webix Datatable - saving and loading by url + + Firestore and Webix Datatable - saving and loading by url - \ No newline at end of file diff --git a/samples/14_firestore_collection.html b/samples/14_firestore_collection.html index b230e03..adbae8a 100644 --- a/samples/14_firestore_collection.html +++ b/samples/14_firestore_collection.html @@ -1,33 +1,29 @@ - - + + - - - - - - - Firebase and Webix Datatable - saving and loading by url + + Firestore and Webix Datatable - saving and loading by url - \ No newline at end of file diff --git a/yarn.lock b/yarn.lock deleted file mode 100644 index fb57ccd..0000000 --- a/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - -