GithubHelp home page GithubHelp logo

Comments (19)

moses5407 avatar moses5407 commented on July 1, 2024 1

NOW I understand the reason for the sandboxed storage. Thanks for that explanation about APP1 calling APP2 and the possible resulting data confusion.

from documentation.

j2l avatar j2l commented on July 1, 2024

Very interesting!
Do you have an example where 2 solves a problem 1 can't?
If not, I prefer 1.

from documentation.

steve21124 avatar steve21124 commented on July 1, 2024

@j2l Yes. flexibility come with complexity. Prefer simple 1 unless no 2 has real benefit.

from documentation.

drwasho avatar drwasho commented on July 1, 2024

Finally got to read this. This looks great and it would solve some of the problems I've been facing!

from documentation.

gliechtenstein avatar gliechtenstein commented on July 1, 2024

@drwasho the reason I came up with option 2 was because you mentioned to me how the first approach wouldn't work for you. Maybe you can weigh in on the conversation on whether option 2 actually solves your problem, and if so, tell us why that works better than option 1 for you, so we can make a better decision moving forward?

from documentation.

drwasho avatar drwasho commented on July 1, 2024

For what I'm building, there were 2 reasons why I decided not to use the option 1 approach:

  1. I had no easy way to clear the state in other views other than to have the user go into those views and either: a) have them remove whatever data persisted there, or b) automatically clear the state every time you open that view (which was a bad idea)

  2. From a data handling POV, it was easier to access and clear variables that are locally scoped, at the price of more complexity in the view

And 2 is the big one. This is particularly relevant when you have views that are submitting a complex form, like an ecommerce listing.

For example, imagine I have a parent view where I set most of the listing data, but I also have a child review where I set the shipping options. If I set shipping options in the child view, then when I go to create a new listing at another time, the shipping options from the previous listings will persist in state. The same would be true for listing options or any other detailed complex data that needs its own child view. Ultimately this means means I’ll end up creating listings with the details of old listings unless some very bad UI/UX is introduced.

Alternatively I prefer to have a complex view + mix-ins (which make it manageable) where all the variables are local and easy to manipulate/clear.

So I think option 2 is the best here, and I'd add that it would be even better if we had an $state.reset action too.

However, my real preference is to have a global key:value storage that is read/write accessible from any view (actually configurable, as in X view can read and Y view can read/write).

This does become a bit of a security headache when you have views that are remotely hosted, but for those of us creating production apps with locally stored views, it is less of a problem.

from documentation.

j2l avatar j2l commented on July 1, 2024

@drwasho, it makes sense.
thanks!
May I ask how you manage the child view of a product? With templates only or views? If views, how do you manage to have them offline?

from documentation.

Joshfindit avatar Joshfindit commented on July 1, 2024

Two comments:

  • Second vote here for the ability to have a global storage per app, with all views inside the app being able to access it, but I do also like this idea of protecting some values behind the export/import scheme.
  • In the data-per-url scheme, would it be beneficial to think of only allowing certain other urls/views to access protected keys?

from documentation.

gliechtenstein avatar gliechtenstein commented on July 1, 2024

In the data-per-url scheme, would it be beneficial to think of only allowing certain other urls/views to access protected keys?

@Joshfindit Yes that was the idea. In case of the second spec (which I am leaning towards), there's an attribute called state, which will just be an array of accessible attributes for the initial release, but eventually it will contain something like a key value pair of security info such as:

state: [{
  "username": {
    "read": "*",
    "write": "https://jasonbase.com/things/3nf.json"
  }
}]

In this case the username attribute is readable from all urls but only writable by the specified URL, or something like that. But I don't want to think too deep into this yet since the first version isn't even out yet. Probably will go with the "allow all" approach as the first version

from documentation.

gliechtenstein avatar gliechtenstein commented on July 1, 2024

OK I made some updates. Here's the new syntax. This will probably be the final version that will roll out unless someone finds a bug or weird cases I haven't thought of.

Before I explain, you can try out a working demo at https://jasonbase.com/things/amWz

Basically the example creates a "chatroom" but everything is stored locally. Note that you need the $state branch code to get this to work.

Anyway, here's the new syntax. Please let me know if you have any thoughts.

Syntax v2.0

Consists of :

  • $state.set
  • $state.get
  • $state.reset

All of the use a key format that looks like this: [key path]@[url]

For example: username@https://www.jasonbase.com/things/3nf.json. This is similar to the partial mixin syntax, but a bit more restricted in that you can ONLY access the root keys.

This means you can't do things like user.username@https://www.jasonbase.com/things/3nf.json. Instead you should just fetch user@https://www.jasonbase.com/things/3nf.json and assign it to a local variable, and then use it.

Here are the details:

$state.set

Setting one attr.

{
  "type": "$state.set",
  "options": {
    "username@https://jasonbase.com/things/3nf.json": "ethan"
  }
}

Setting multiple attrs

{
  "type": "$state.set",
  "options": {
    "username@https://jasonbase.com/things/3nf.json": "ethan",
    "email@https://jasonbase.com/things/3nf.json": "[email protected]"
  }
}

$state.get

Getting one attr. In this case, it retrieves the stored value at username@https://jasonbase.con/cnf.json and then passes it to the succeeding action as $jason.username.

{
  "type": "$state.get",
  "options": {
    "username": "username@https://jasonbase.com/cnf.json"
  }
}

Getting multiple attrs. Like above, it fetches the value and passes them down to the succeeding action as {"$jason": {"username": "...", "email": "..."}}.

{
  "type": "$state.get",
  "options": {
    "username": "username@https://jasonbase.com/cnf.json",
    "email": "email@https://jasonbas.com/chnf.json"
  }
}

$state.reset

Resetting a single attr. Notice the options takes an array.

{
  "type": "$state.reset",
  "options": [ "username@https://jasonbase.com/things/3nf.json" ]
}

Resetting multiple attrs

{
  "type": "$state.reset",
  "options": [ "username@https://jasonbase.com/things/3nf.json", "email@https://jasonbase.com/things/33n.json" ]
}

from documentation.

gliechtenstein avatar gliechtenstein commented on July 1, 2024

Syntax v3.0

OK today I have another proposal. The v2.0 definitely works but I wasn't completely satisfied (especially the $state.get part) so have been trying to come up with a couple more ideas.

So this time I tried to come up with a syntax that will:

  1. Get rid of the email-address-like scheme to refer to various states
  2. Use a variable instead of an action when retrieving state

Anyway, here it goes:

A. Setting state

In this version, $state.set uses a full hierarchy instead of using the email address-like syntax (for example username@https://jasonbase.com/things/3nf.json)

1. A single attribute for a single source

{
  "type": "$state.set",
  "options": {
    "https://jasonbase.com/things/3nf.json": {
      "username": "ethan"
    }
  }
}

2. Multiple attributes from a single source

{
  "type": "$state.set",
  "options": {
    "https://jasonbase.com/things/3nf.json": {
      "username": "ethan",
      "email": "[email protected]"
    }
  }
}

3. Multiple attributes from multiple sources

{
  "type": "$state.set",
  "options": {
    "https://jasonbase.com/things/3nf.json": {
      "username": "ethan",
      "email": "[email protected]"
    },
    "https://jasonbase.com/things/333.json": {
      "lastUpdated": "{{Date.now().toString()}}"
    }
  }
}

B. Getting state

One thing I didn't like about previous approaches was you have to manually call $state.get everytime you need to read some state. This became quite verbose when I tried using it in a real world app, so I decided to try the approach used by cache and local variables--instead of using an action, directly access them using a special variable:

{
  "type": "label",
  "text": "{{$state['https://jasonbase.com/things/cnf.json'].username}}"
}

If the URL feels too bulky we can always move this to $load and initialize them into local variables, like this:

{
  "$jason": {
    "head": {
      "actions": {
        "$load": {
          "type": "$set",
          "options": {
            "db": "{{$state['https://jasonbase.com/things/cnf.json']}}"
          },
          "success": {
            "type": "$render"
          }
        }
      },
      "templates": {
        "body": {
          "sections": [{
            "items": [{
              "type": "label",
              "text": "{{$get.db.username}}"
            }]
          }]
        }
      }
    }
  }
}

C. Resetting State

1. Resetting a single attribute from a single source

{
  "type": "$state.reset",
  "options": {
    "https://jasonbase.com/things/33n.json": ["email"]
  }
}

2. Resetting multiple attributes from a single source

{
  "type": "$state.reset",
  "options": {
    "https://jasonbase.com/things/3nf.json": ["username", "email"]
  }
}

3. Resetting multiple attributes from multiple sources

{
  "type": "$state.reset",
  "options": {
    "https://jasonbase.com/things/3nf.json": ["username", "email"],
    "https://jasonbase.com/things/33n.json": ["lastUpdated"]
  }
}

If anyone sees something weird, please point out. Otherwise i'm gonna try to write an implementation for this in the next couple of days and test it out. Thanks

from documentation.

moses5407 avatar moses5407 commented on July 1, 2024

I'm sure I'm missing a LOT .. but why not json data into and out of indexeddb something like this?
https://stackoverflow.com/questions/31703419/how-to-import-json-file-into-indexeddb

from documentation.

gliechtenstein avatar gliechtenstein commented on July 1, 2024

@moses5407 the problem is more about "how" to express the get/set/reset actions in pure JSON markup, than how to implement it internally.

The implementation itself is easy like you said, it will be pretty much about storing JSON under the URL namespace. What's been difficult was coming up with a concise and consistent way of expressing these actions in pure JSON.

Or maybe I misunderstood what you said? Could you clarify if I got your idea wrong? Thanks!

from documentation.

moses5407 avatar moses5407 commented on July 1, 2024

You didn't misunderstand. :-)

In my simple mind, I was envisioning simply iterating over any changed data set and updating the indexeddb datastore or get-ing data from the indexeddb datastore in an offline condition.

I'm slowwwwwly trying to get up to speed on a number of things and offline first is only one of them.
In the process of looking into indexeddb I came across things like the following and it seemed pretty straightforward. Probably, my ignorance is (unrealistic) bliss. Hahaha!

var objstore = db.transaction([STORE], "readwrite").objectStore(STORE);
for (i = 0; i < data.length; i++) {
objstore.put(data[i]);
}

from documentation.

drwasho avatar drwasho commented on July 1, 2024

Hey @gliechtenstein

1. Setting state

  • Makes a great deal of sense and I like it

2. Getting state

so I decided to try the approach used by cache and local variables--instead of using an action, directly access them using a special variable

👏 Masterpiece

3. Reseting state

Makes sense. LGTM.

from documentation.

hoffmabc avatar hoffmabc commented on July 1, 2024

@gliechtenstein could you just use "global" as your namespace for the cache and write it from wherever? I would assume this latest model allows that?

from documentation.

gliechtenstein avatar gliechtenstein commented on July 1, 2024

@hoffmabc it's for security reasons. Jasonette is basically a browser for apps. This means it is possible to cross over between different apps (the soul) within the same app (the shell). I think this is one of the biggest strengths of Jasonette, but at the same time can be a security hole. I think it's easy to understand if you think in parallel with how web browsers work. They have localstorage but each localstorage is sandboxed to the parent URL.

Here's a hypothetical scenario where it could go wrong if we didn't have the sandbox model:

  1. App 1 stores some data in the global storage
  2. App 1 opens App 2 via href
  3. App 2 now has access to the global storage set by App 1.

App 1 may or may not have intended it, but the point is that there is no way to restrict this if we keep a single global storage.

The idea was that, by tying these with URL it's much easier to handle permissions (For example App 1 can decide to allow access to its own "global" storage to only a selected set of URLs)

This can be a powerful feature for an app, because you can allow access to the master localstorage from child views (only if you want), as well as specify strict permission policy between views, allowing for a more decentralized and customized implementation of apps.

Hope this makes sense. If you have any suggestions or different ideas please share.

from documentation.

gliechtenstein avatar gliechtenstein commented on July 1, 2024

OK I change my mind.

I have decided to roll out the "global variable" everyone wants, instead of trying to keep trying to come up with the best solution for a sandboxed storage. It's much simpler than everything I've mentioned above because it's just a single global scope, and there is no URL.

Example syntax:

Setting:

{
  "type": "$global.set",
  "options": {
    "db": ["a", "b", "c", "d"]
  }
}

Then you can use the db variable from anywhere in the app using:

{
  "items": {
    "{{#each $global.db}}": {
      "type": "label",
      "text": "{{this}}"
    }
  }
}

You can also remove the key from the global namespace:

{
  "type": "$global.reset",
  "options": {
    "items": ["db"]
   }
 }

The reason I changed my mind is:

  1. I've talked to quite a few users about this feature, and from the feedback, the only thing people want for now is a dead simple way to store stuff globally so they can exist across views.
  2. I do want to implement the sandboxed local storage because global variables are bad and the sandboxed model will be much more powerful due to its privacy and security model, BUT for now there are too many things to think about.
  3. So I will release the global storage as a "true global storage". And hopefully figure out the best way to implement the sandboxed storage based on its usage.

The biggest benefit of this unrestricted global variable approach is that it's as simple as it can get, which is what everyone would want I presume.

Anyway here's a simple TODO app using the global variable where the parent view opens a child view as a modal where it adds an item and returns back to the parent.

I think this feature will make $util.picker and a lot of other widgets unnecessary because you can simply present a custom view to use as input instead of using these widgets, which was one of the most frequently asked questions--how to customize pickers and built-in widgets

Anyway, check out the branch: https://github.com/Jasonette/JASONETTE-iOS/tree/global

The code is annotated to show you how to use them (Jasonette/JASONETTE-iOS@525bbe9) and you can also refer to the example I shared above.

I plan to merge this soon, because this is super simple and there's not much that can go wrong with this. But if anyone has feedback on anything such as the naming (currently it uses $global but if you have a better idea let me know) or anything else, please let me know before I finalize.

Thanks!

from documentation.

j2l avatar j2l commented on July 1, 2024

I also get the reasons behind your initial setup. Thanks

from documentation.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.